基于FPGA的以太网设计(五)

之前简单介绍并实现了ARP协议,今天简单介绍一下IP协议和ICMP协议。

1.IP协议

IP协议即Internet Protocol是网络层的协议。

IP协议是TCP/IP协议族的核心协议,其主要包含两个方面:

  • IP头部信息。IP头部信息出现在每个IP数据报中,用于指定IP通信的源端IP地址、目的端IP地址,指导IP分片和重组,以及指定部分通信行为。
  • IP数据报的路由和转发。IP数据报的路由和转发发生在除目标机器之外的所有主机和路由器上。它们决定数据报是否应该转发以及如何转发。

IPv4的头部结构如下所示,其长度通常为20个字节(最多60个字节),除非含有可变长的选项部分

版本号4位,用于指定ip协议的版本。

IHL(4位):标识该IP头部有多少个32bit字(4字节),此处默认头部长度为20字节,故IHL为0101

服务类型(8位):包括一个3位的优先权字段,4位的TOS字段和1位的保留字段(必须置0)。4位的TOS字段分别表示:最小延时,最大吞吐量,最高可靠性和最小费用。其中最多有一个能置位1,应用程序应该根据实际需要来设置它。

总长度(16位)是指整个IP数据报的长度,以字节为单位,因此IP数据报的最大长度为65535字节。但由于MTU的限制,长度超过MTU的数据报都将被分片传输,所以实际传输的IP数据报的长度都远远没有达到最大值。

标识符(16位)唯一标识主机发送的每一个数据报

标志字段(3位)地第一位保留。第二位表示"禁止分片"。如果设置了这个位,IP模块将不对数据报进行分片。在这种情况下,如果IP数据报长度超过MTU的话,IP模块将丢弃该数据报并返回一个ICMP差错报文。第三位表示“更多分片”。除了数据报的最后一个分片外,其他分片都要把它置1。

分片偏移(13位)是分片相对原始IP数据报开始处(仅指数据部分)的偏移。实际的偏移值是该值左移3位(乘8)后得到的。由于这个原因,除了最后一个IP分片外,每个IP分片的数据部分的长度必须是8的整数倍(这样才能保证后面的IP分片拥有一个合适的偏移量)。

生存时间(即TTL)(8位)是数据报到达目的地之前允许经过的路由器跳数。TTL值被发送端设置(常见值位64)。数据报在转发过程中每经过一个路由,该值就被路由器减1。当TTL值减为0时,路由器将丢弃数据报,并向源端发送一个ICMP差错报文。TTL值可以防止数据报陷入路由循环。

协议(8位)用来区分上层协议,/etc/protocols文件定义了所有上层协议对应的protocol字段的数值。其中ICMP1TCP6UDP17

头部校验和(16位)由发送端填充,接收端对其使用CRC算法以检验IP数据报头部在传输过程中是否损坏。

源端IP地址(32位)和目的端IP地址(32位)用来标识数据报的发送端和接收端。一般情况下,这两个地址在整个数据报的传递过程中保持不变,而不论它中间经过多少个中转路由器。

IPv4最后一个选项字段是可变长的可选信息。这部分最多包含40个字节,因为IP头部最长是60字节(其中还包含前面讨论的20字节的固定部分)

通过下图分析一下首部校验和的计算规则:

首部校验和的计算规则为:首先假设16位检验和为0,然后将IP首部按照16位分成多个单元,把所有单元相加,如果得到的低16位数据出现进位,则把低16位数据与高16位数据相加,相加后如果低16位还有进位,则继续把低16位与高16位数据相加,然后低16位数据取反得到首部校验和。

例如上图发送总长度为500个字节的IP数据报,发送端IP地址为192.168.1.189,接收端IP地址为192.168.1.10,则IP首部数据如下:

  按照上述提到的 IP 首部校验和的方法计算 IP 首部校验和,即:

  16’h4500 + 16’h01f4 + 16’h0000 + 16’h4000 + 16’h4001 + 16’h0000(计算时为0) + 16’hc0a8 + 16’h01bd + 16’hc0a8 + 16’h010a = 32’h00024b0c(低16位出现进位)。

  16’h0002 + 16’h4b0c = 32’h00004b0e(低16位未出现进位)。

  16’h0000 + 16‘h4b0e = 32’h00004b0e(低16位未出现进位)

  最终得到校验码check_sum = ~16‘h4b0e = 16’b4f1

  其实第一次相加低16位可能出现进位,取相加后的低16位与高16位相加结果也可能出现进位,相加结果的高低位相加两次后就不可能出现进位了,所以在程序设计时,就直接把第一次的计算结果的高16位数据与低16位数据相加两次后得到的低16位数据取反,得到首部校验和,省去判断进位的步骤,简化代码。

2.ICMP协议

ICMP协议是网络层协议,封装在IP数据报中。主要功能就是ping命令和tracert命令,可以检查网络的连通性和显示经过路径。

ICMP协议是TCP/IP 模型中网络层的重要成员,与 IP 协议、ARP 协议、RARP 协议及 IGMP 协议共同构成 TCP/IP 模型中的网络层ping 主机的ip后,得到不同的回复,对应不同的结果。

  • 无法访问目标主机:没有网关或缺少目标主机MAC
  • 请求超时:对方不在线或屏蔽 (即对方防火墙拦截)
  • 传输失败:未获取MAC地址,导致无法进行ICMP封装

ICMP首部总共8个字节,所以可以通过IP首部的总长度和IP首部长度、ICMP首部长度计算出ICMP数据长度。

类型(type):8位数表示错误类型的差错报文或者查询类型的报告报文。通常与代码结合使用

代码(code):占用8位数据,根据ICMP差错报文的类型,进一步分析错误的原因。下表就是代码和类型部分组合的含义,后文主要使用回显请求和回显应答。

种类

类型

代码

报文含义

查询报文

0

0

回显应答(ping应答)

查询报文

8

0

回显请求(ping请求)

差错报文

3

0

网络不可达

差错报文

3

1

主机不可达

差错报文

3

2

协议不可达

差错报文

3

3

端口不可达

差错报文

3

7

目的主机未知

差错报文

12

0

坏的IP首部

差错报文

12

1

缺少必须选项

校验和(checksum):16 位校验和的计算方法与IP首部校验和计算方法一致,该校验和需要对ICMP首部和ICMP数据做校验。

标识符(Identifier):16位标识符对每一个发送的数据报进行标识。

序列号(Sequence number):16位对发送的每一个数据报文进行编号。

标识符和序列号其实是为了区分相同类型的不同两个数据报,比如主机A向主机B发送了两个回显请求,然后主机B针对两个回显请求分别做了回显应答。

回显应答的标识符和序列号必须与回显请求中的标识符和序列号保持一致, 这样主机A收到回显应答后,才知道主机B响应的是哪一个回显请求,进而对比回显应答数据与回显请求的数据是否一致,一致则表示ping成功,否则失败。

数据(Data):要发送的ICMP数据,注意回显应答数据段的数据必须与回显请求数据段的数据保持一致,否则ping失败。

总体的ICMP数据报格式如下图:

  1. 3.代码实现

3.1发送部分:

`timescale 1ns / 1psmodule ICMP_TX(input                                   clk                         ,   //系统时钟input                                   rst_n                       ,   //系统复位//input         input                   [31:0]          reply_checksum              ,   //ICMP数据部分校验和input                   [15:0]          icmp_id                     ,   //ICMP标识符input                   [15:0]          icmp_seq                    ,   //ICMP序列符input                                   tx_start                    ,   //以太网发送开始信号input                   [7:0]           tx_data                     ,   //以太网发送数据input                   [15:0]          tx_byte_num                 ,   //以太网发送有效字节数input                   [31:0]          des_ip                      ,   //目的ip地址input                   [47:0]          des_mac                     ,   //目的MAC地址input                   [31:0]          crc_data                    ,   //CRC校验数据input                   [7:0]           crc_next                    ,   //CRC下次校验数据//output            output              reg                 tx_done                     ,   //以太网发送完成信号output              reg                 tx_req                      ,   //以太网发送请求信号output              reg                 gmii_tx_en                  ,   //GMII发送使能信号output              reg [7:0]           gmii_txd                    ,   //GMII发送数据output              reg                 crc_en                      ,   //CRC校验使能信号output              reg                 crc_clr                         //CRC清零信号    
);parameter       BOARD_IP        =       {8'd192,8'd168,8'd1,8'd10}  ;   //开发板ip地址parameter       BOARD_MAC       =       48'h00_11_22_33_44_55       ;   //开发板MAC地址parameter       DES_IP          =       {8'd192,8'd168,8'd1,8'd102} ;   //源ip地址parameter       DES_MAC         =       48'hff_ff_ff_ff_ff_ff       ;   //源MAC地址设置为广播地址localparam      IDLE            =       8'b0000_0001                ;   //空闲状态localparam      CHECK_SUM       =       8'b0000_0010                ;   //ip首部校验和localparam      CHECK_ICMP      =       8'b0000_0100                ;   //ICMP首部加校验数据       localparam      PREAMBLE        =       8'b0000_1000                ;   //发送前导码加帧起始界定符localparam      ETH_HEAD        =       8'b0001_0000                ;   //发送以太网帧头localparam      IP_HEAD         =       8'b0010_0000                ;   //发送ip首部加ICMP首部localparam      TX_DATA         =       8'b0100_0000                ;   //发送数据localparam      CRC             =       8'b1000_0000                ;   //发送CRC校验值localparam      ETH_TYPE        =       16'h0800                    ;   //以太网类型localparam      MIN_DATA_NUM    =       16'd18                      ;   //以太网数据最小46字节,ip首部20字节+ICMP首部8字节parameter       ECHO_REPLY      =       8'h00                       ;   //ICMP报文类型:回显应答reg                                     state_en                    ;   //状态跳转使能reg             [7:0]                   cur_state                   ;   //现态reg             [7:0]                   next_state                  ;   //次态reg                                     tx_start_r0                 ;   reg                                     tx_start_r1                 ;   reg                                     tx_start_r2                 ;   reg             [15:0]                  tx_data_num                 ;   //发送有效数据字节reg             [15:0]                  total_num                   ;   //总字节数据reg                                     trig_tx_en                  ;   //触发发送信号reg             [4:0]                   cnt                         ;   //计数器reg             [7:0]                   preamble[7:0]               ;   //前导码reg             [7:0]                   eth_head[13:0]              ;   //以太网首部reg             [31:0]                  ip_head[6:0]                ;   //IP首部 + ICMP首部               reg             [31:0]                  check_buffer                ;   //ip首部校验和reg             [31:0]                  check_buffer_icmp           ;   //ip首部校验和reg             [1:0]                   tx_bit_sel                  ;   //发送字节选择reg             [15:0]                  data_cnt                    ;   //发送字节计数器reg                                     tx_done_t                   ;   //发送结束信号reg             [4:0]                   real_add_cnt                ;   //以太网实际多发字节数据wire                                    posedge_tx_start            ;   //发送开始信号上升沿 wire            [15:0]                  real_tx_data_num            ;   //实际发送数据的数量assign  posedge_tx_start = ~(tx_start_r2) && tx_start_r1;       assign  real_tx_data_num = (tx_data_num >= MIN_DATA_NUM) ? tx_data_num : MIN_DATA_NUM;//检测发送开始信号上升沿always @(posedge clk or negedge rst_n) beginif(!rst_n)begintx_start_r0 <= 1'b0;tx_start_r1 <= 1'b0;tx_start_r2 <= 1'b0;endelse begintx_start_r0 <= tx_start;tx_start_r1 <= tx_start_r0;tx_start_r2 <= tx_start_r1;endend//寄存数据有效字节always @(posedge clk or negedge rst_n) beginif(!rst_n)begintx_data_num <= 16'd0;total_num <= 16'd0;endelse if(posedge_tx_start && cur_state == IDLE)begintx_data_num <= tx_byte_num;total_num <= tx_byte_num + 16'd28;endelse;end//触发发送信号always @(posedge clk or negedge rst_n) beginif(!rst_n)begintrig_tx_en <= 1'b0;endelse begintrig_tx_en <= posedge_tx_start;endend//三段式状态机第一段always @(posedge clk or negedge rst_n) beginif(!rst_n)cur_state <= IDLE;elsecur_state <= next_state;end//三段式状态机第二段always @(*) beginnext_state = IDLE;case (cur_state)IDLE: beginif(state_en)next_state = CHECK_SUM;elsenext_state = IDLE;endCHECK_SUM: beginif(state_en)next_state = CHECK_ICMP;elsenext_state = CHECK_SUM;  endCHECK_ICMP: beginif(state_en)next_state = PREAMBLE;elsenext_state = CHECK_ICMP;endPREAMBLE: beginif(state_en)next_state = ETH_HEAD;else next_state = PREAMBLE;endETH_HEAD: beginif(state_en)next_state = IP_HEAD;elsenext_state = ETH_HEAD;endIP_HEAD: beginif(state_en)next_state = TX_DATA;elsenext_state = IP_HEAD; endTX_DATA: beginif(state_en)next_state = CRC; elsenext_state = TX_DATA;endCRC: beginif(state_en)next_state = IDLE;elsenext_state = CRC; enddefault: ;endcaseend//三段式状态机第三段always @(posedge clk or negedge rst_n) beginif(!rst_n)beginstate_en <= 1'b0;check_buffer <= 32'd0;check_buffer_icmp <= 32'd0;cnt <= 5'd0;ip_head[1][31:16] <= 16'd0;//标识位tx_bit_sel <= 2'd0;crc_en <= 1'b0;gmii_tx_en <= 1'b0;gmii_txd <= 8'd0;tx_done_t <= 1'b0;tx_req <= 1'b0;data_cnt <= 16'd0;real_add_cnt <= 5'd0;//初始化数组//前导码+帧起始界定符preamble[0] <= 8'h55;preamble[1] <= 8'h55;preamble[2] <= 8'h55;preamble[3] <= 8'h55;preamble[4] <= 8'h55;preamble[5] <= 8'h55;preamble[6] <= 8'h55;preamble[7] <= 8'hd5;//目的MAC地址eth_head[0] <= DES_MAC[47:40];eth_head[1] <= DES_MAC[39:32];eth_head[2] <= DES_MAC[31:24];eth_head[3] <= DES_MAC[23:16];eth_head[4] <= DES_MAC[15:8];eth_head[5] <= DES_MAC[7:0];//源MAC地址eth_head[6] <= BOARD_MAC[47:40];eth_head[7] <= BOARD_MAC[39:32];eth_head[8] <= BOARD_MAC[31:24];eth_head[9] <= BOARD_MAC[23:16];eth_head[10] <= BOARD_MAC[15:8];eth_head[11] <= BOARD_MAC[7:0];//以太网类型eth_head[12] <= ETH_TYPE[15:8];eth_head[13] <= ETH_TYPE[7:0];endelse beginstate_en <= 1'b0;crc_en <= 1'b0;gmii_tx_en <= 1'b0;tx_done_t <= 1'b0;case (next_state)IDLE: beginif(trig_tx_en)beginstate_en <= 1'b1;//版本号:4 首部长度:5ip_head[0] <= {8'h45,8'h00,total_num};//16位标识,每次发送累加1ip_head[1][31:16] <= ip_head[1][31:16] + 1'b1;//bit[15:13]为010表示不分片ip_head[1][15:0] <= 16'h4000;//8'h80表示生存时间//8'd01:1代表ICMP,2代表IGMP,6代表TCP,17代表UDPip_head[2] <= {8'h80,8'h01,16'h0000};ip_head[3] <= BOARD_IP;//目的ip地址if(des_ip != 32'd0)ip_head[4] <= des_ip;elseip_head[4] <= DES_IP;//8位 icmp type,8位 icmp codeip_head[5][31:16] <= {ECHO_REPLY,8'h00};//16位标识符 16位序列号ip_head[6] <= {icmp_id,icmp_seq};//更新MAC地址if(des_ip != 48'd0)begineth_head[0] <= des_mac[47:40];eth_head[1] <= des_mac[39:32];eth_head[2] <= des_mac[31:24];eth_head[3] <= des_mac[23:16];eth_head[4] <= des_mac[15:8];eth_head[5] <= des_mac[7:0];endelse;endelse;end CHECK_SUM: begincnt <= cnt + 1'b1;if(cnt == 5'd0)begincheck_buffer <= ip_head[0][31:16] + ip_head[0][15:0]+ip_head[1][31:16] + ip_head[1][15:0]+ip_head[2][31:16] + ip_head[2][15:0]+ip_head[3][31:16] + ip_head[3][15:0]+ip_head[4][31:16] + ip_head[4][15:0];endelse if(cnt == 5'd1)begin//可能出现进位,高16位、低16位进行相加check_buffer <= check_buffer[31:16] + check_buffer[15:0];endelse if(cnt == 5'd2)begin//可能再次出现进位,再次相加check_buffer <= check_buffer[31:16] + check_buffer[15:0];endelse if(cnt == 5'd3)begincnt <= 5'd0;state_en <= 1'b1;ip_head[2][15:0] <= ~check_buffer[15:0];//按位取反endelse;endCHECK_ICMP: begin//ICMP首部+数据校验cnt <= cnt + 1'b1;if(cnt == 5'd0)begincheck_buffer_icmp <= ip_head[5][31:16] + ip_head[6][31:16]+ip_head[6][15:0] + reply_checksum;endelse if(cnt == 5'd1)begincheck_buffer_icmp <= check_buffer_icmp[31:16] + check_buffer_icmp[15:0];endelse if(cnt == 5'd2)begincheck_buffer_icmp <= check_buffer_icmp[31:16] + check_buffer_icmp[15:0];endif(cnt == 5'd3)begincnt <= 5'd0;state_en <= 1'b1;//ICMP 16位校验和ip_head[5][15:0] <= ~check_buffer_icmp[15:0];endelse;endPREAMBLE: begingmii_tx_en <= 1'b1;gmii_txd <= preamble[cnt];if(cnt == 5'd7)begincnt <= 5'd0;state_en <= 1'b1;endelsecnt <= cnt + 1'b1;endETH_HEAD:begingmii_tx_en <= 1'b1;gmii_txd <= eth_head[cnt];crc_en <= 1'b1;if(cnt == 5'd13)begincnt <= 5'd0;state_en <= 1'b1;endelsecnt <= cnt + 1'b1;endIP_HEAD: begincrc_en <= 1'b1;gmii_tx_en <= 1'b1;tx_bit_sel <= tx_bit_sel + 1'b1;if(tx_bit_sel == 2'd0)gmii_txd <= ip_head[cnt][31:24];else if(tx_bit_sel == 2'd1)gmii_txd <= ip_head[cnt][23:16];else if(tx_bit_sel == 2'd2)begingmii_txd <= ip_head[cnt][15:8];if(cnt == 5'd6)tx_req <= 1'b1;//提前拉高发送请求数据,数据有效就将数据发送else;endelse if(tx_bit_sel == 2'd3)begingmii_txd <= ip_head[cnt][7:0];if(cnt == 5'd6)begincnt <= 5'd0;state_en <= 1'b1;endelsecnt <= cnt + 1'b1;endelse;endTX_DATA: begincrc_en <= 1'b1;gmii_tx_en <= 1'b1;gmii_txd <= tx_data;tx_bit_sel <= 3'd0;if(data_cnt < tx_data_num - 1'b1)begindata_cnt <= data_cnt + 1'b1;endelse if(data_cnt == tx_data_num - 1'b1)beginif(data_cnt + real_add_cnt < real_tx_data_num - 1'b1)real_add_cnt <= real_add_cnt + 1'b1;else beginstate_en <= 1'b1;data_cnt <= 16'd0;real_add_cnt <= 'd0;endendelse;if(data_cnt == tx_data_num - 16'd2)tx_req <= 1'b0;else;endCRC:begingmii_tx_en <= 1'b1;tx_bit_sel <= tx_bit_sel + 1'b1;tx_req <= 1'b0;if(tx_bit_sel == 3'd0)gmii_txd <= {~crc_next[0], ~crc_next[1], ~crc_next[2],~crc_next[3],~crc_next[4], ~crc_next[5], ~crc_next[6],~crc_next[7]};else if(tx_bit_sel == 3'd1)gmii_txd <= {~crc_data[16], ~crc_data[17], ~crc_data[18],~crc_data[19],~crc_data[20], ~crc_data[21], ~crc_data[22],~crc_data[23]};else if(tx_bit_sel == 3'd2) begingmii_txd <= {~crc_data[8], ~crc_data[9], ~crc_data[10],~crc_data[11],~crc_data[12], ~crc_data[13], ~crc_data[14],~crc_data[15]};endelse if(tx_bit_sel == 3'd3) begingmii_txd <= {~crc_data[0], ~crc_data[1], ~crc_data[2],~crc_data[3],~crc_data[4], ~crc_data[5], ~crc_data[6],~crc_data[7]};tx_done_t <= 1'b1;state_en <= 1'b1;endelse;enddefault:;endcaseendend//发送完成信号及crc复位信号always @(posedge clk or negedge rst_n) beginif(!rst_n)begintx_done <= 1'b0;crc_clr <= 1'b0;endelse begintx_done <= tx_done_t;crc_clr <= tx_done_t;endendendmodule

仿真结果如下所示:

检测到开始发送信号上升沿,触发信号拉高,进入校验和检测状态。

累加结果。

发送以太网帧头部分不再赘述,因为之前就已经详细说过,重点关注ip首部发送过程。需要注意的是ip首部总共包含20字节数据,并且一定要搞清楚每一部分的含义是啥。加上ICMP首部总共是28字节,发送完成之后再发送20字节数据,最后进入CRC状态。

3.2接收部分:

`timescale 1ns / 1psmodule ICMP_RX(input                               clk             ,   //系统时钟input                               rst_n           ,   //系统复位//input input                               gmii_rx_dv      ,   //接收数据有效信号input               [7:0]           gmii_rxd        ,   //接收数据//output    output          reg                 rec_pkt_done    ,   //以太网单包数据接收完成信号output          reg                 rec_en          ,   //接收使能信号output          reg [7:0]           rec_data        ,   //以太网接收数据output          reg [15:0]          rec_byte_num    ,   //以太网接收的有效字节数output          reg [15:0]           icmp_id         ,   //ICMP标识符output          reg [15:0]           icmp_seq        ,   //ICMP序列号output          reg [31:0]          reply_checksum      //接收数据校验                  
);parameter       BOARD_IP    =       {8'd192,8'd168,8'd1,8'd10};parameter       BOARD_MAC   =       48'h00_11_22_33_44_55;localparam      IDLE        =       7'b000_0001     ;   //空闲状态    localparam      PREAMBLE    =       7'b000_0010     ;   //接收前导码和帧起始界定符localparam      ETH_HEAD    =       7'b000_0100     ;   //接收以太网帧头localparam      IP_HEAD     =       7'b000_1000     ;   //接收ip帧头localparam      ICMP_HEAD   =       7'b001_0000     ;   //接收ICMP帧头localparam      RX_DATA     =       7'b010_0000     ;   //接收数据localparam      RX_DONE     =       7'b100_0000     ;   //接收数据结束localparam      ETH_TYPE    =       16'h0800        ;   //以太网类型localparam      ICMP_TYPE   =       8'd1            ;   //ICMP协议类型localparam      ECHO_REQUEST=       8'h08           ;   //ICMP报文类型回显请求reg             [6:0]               cur_state       ;   //现态reg             [6:0]               next_state      ;   //次态reg                                 state_en        ;   //状态跳转使能信号   reg                                 error_flag      ;   //错误指示信号reg             [4:0]               cnt             ;   //解析数据计数器reg             [47:0]              des_mac         ;   //目的MAC地址reg             [31:0]              des_ip          ;   //目的ip地址reg             [15:0]              eth_type        ;   //以太网类型reg             [5:0]               ip_head_byte_num;   //ip首部长度reg             [15:0]              total_length    ;   //ip长度reg             [1:0]               rec_en_cnt      ;   //8位转32位计数器reg             [7:0]               icmp_type       ;   //ICMP报文类型:用于表示错误类型的差错报文或查询类型的报告论文reg             [7:0]               icmp_code       ;   //ICMP报文代码:根究差错报文查询错误原因reg             [15:0]              icmp_checksum   ;   //接收数据校验和:数据被接收后需要对ICMP数据报文做一个校验,用于检查数据是否出错      reg             [15:0]              icmp_data_length;   reg             [15:0]              icmp_rx_cnt     ;   //接收数据计数reg             [7:0]               icmp_rx_data_d0 ;     reg             [31:0]              reply_checksum_add; // //三段式状态机第一段always @(posedge clk or negedge rst_n) beginif(!rst_n)cur_state <= IDLE;elsecur_state <= next_state;end//三段式状态机第二段always @(*) beginnext_state = IDLE;case (cur_state)IDLE: beginif(state_en)next_state = PREAMBLE;else if(error_flag)next_state = RX_DONE;elsenext_state = IDLE;end PREAMBLE:beginif(state_en)next_state = ETH_HEAD;else if(error_flag)next_state = RX_DONE;elsenext_state = PREAMBLE; endETH_HEAD:beginif(state_en)next_state = IP_HEAD;else if(error_flag)next_state = RX_DONE;elsenext_state = ETH_HEAD; endIP_HEAD:beginif(state_en)next_state = ICMP_HEAD;else if(error_flag)next_state = RX_DONE;elsenext_state = IP_HEAD; endICMP_HEAD:beginif(state_en)next_state = RX_DATA;else if(error_flag)next_state = RX_DONE;elsenext_state = ICMP_HEAD; endRX_DATA:beginif(state_en)next_state = RX_DONE;elsenext_state = RX_DATA; endRX_DONE:beginif(state_en)next_state = IDLE;elsenext_state = RX_DONE;enddefault:next_state = IDLE;endcaseend//三段式状态机第三段always @(posedge clk or negedge rst_n) beginif(!rst_n)beginstate_en <= 1'b0;error_flag <= 1'b0;cnt <= 5'd0;des_ip <= 32'd0;des_mac <= 48'd0;eth_type <= 16'd0;ip_head_byte_num <= 6'd0;total_length <= 16'd0;icmp_type <= 8'd0;icmp_code <= 8'd0;icmp_checksum <= 16'd0;icmp_id <= 16'd0;icmp_seq <= 16'd0;icmp_rx_data_d0 <= 8'd0;reply_checksum <= 32'd0;reply_checksum_add <= 32'd0;icmp_rx_cnt <= 16'd0;icmp_data_length <= 16'd0;rec_en_cnt <= 2'd0;rec_en <= 1'b0;rec_data <= 32'd0;rec_pkt_done <= 1'b0;rec_byte_num <= 16'd0;endelse beginstate_en <= 1'b0;error_flag <= 1'b0;rec_pkt_done <= 1'b0;case (next_state)IDLE:beginif((gmii_rx_dv) && gmii_rxd == 8'h55)state_en <= 1'b1;else;end PREAMBLE:beginif(cnt < 5'd6 && gmii_rxd != 8'h55)error_flag <= 1'b1;else if(cnt == 5'd6)begincnt <= 5'd0;if(gmii_rxd == 8'hd5)state_en <= 1'b1;elseerror_flag <= 1'b1;endelsecnt <= cnt + 1'b1;endETH_HEAD:beginif(gmii_rx_dv)begincnt <= cnt + 1'b1;if(cnt < 5'd6)des_mac <= {des_mac[39:0],gmii_rxd};//接收目的ip地址else if(cnt == 5'd12)eth_type[15:8] <= gmii_rxd;//接收以太网协议类型else if(cnt == 5'd13)begineth_type[7:0] <= gmii_rxd;cnt <= 5'd0;if((des_mac == BOARD_MAC || des_mac == 48'hff_ff_ff_ff_ff_ff) &&eth_type[15:8] == ETH_TYPE[15:8] && gmii_rxd == ETH_TYPE[7:0])//判断ip地址和以太网协议类型state_en <= 1'b1;elseerror_flag <= 1'b1;endelse;endelse;endIP_HEAD:beginif(gmii_rx_dv)begin//ip帧头位20字节cnt <= cnt + 1'b1;if(cnt == 5'd0)ip_head_byte_num <= {gmii_rxd[3:0],2'h00};else if(cnt == 5'd2)total_length[15:8] <= gmii_rxd;//接收总长度else if(cnt == 5'd3)total_length[7:0] <= gmii_rxd; else if(cnt == 5'd4)beginicmp_data_length <= total_length - 16'd28;//ip帧头20字节+ICMP帧头8字节endelse if(cnt == 5'd9)begin//如果当前接收协议不是ICMP协议则停止解析协议if(gmii_rxd != ICMP_TYPE)beginerror_flag <= 1'b1;cnt <= 5'd0;endelse;endelse if((cnt >= 5'd16) && (cnt <= 5'd18))des_ip <= {des_ip[23:0],gmii_rxd};else if(cnt == 5'd19)begindes_ip <= {des_ip[23:0],gmii_rxd};if((des_ip[23:0] == BOARD_IP[31:8]) && gmii_rxd <= BOARD_IP[7:0])beginstate_en <= 1'b1;cnt <= 5'd0;endelse begin//ip不是开发板地址error_flag <= 1'b1;cnt <= 5'd0;endendelse;endelse;endICMP_HEAD:beginif(gmii_rx_dv)begincnt <= cnt + 1'b1;if(cnt == 5'd0)icmp_type <= gmii_rxd;else if(cnt == 5'd1)icmp_code <= gmii_rxd;else if(cnt == 5'd2)icmp_checksum[15:8] <= gmii_rxd;else if(cnt == 5'd3)icmp_checksum[7:0] <= gmii_rxd;else if(cnt == 5'd4)icmp_id[15:8] <= gmii_rxd;else if(cnt == 5'd5)icmp_id[7:0] <= gmii_rxd;else if(cnt == 5'd6)icmp_seq[15:8] <= gmii_rxd;else if(cnt == 5'd7)beginicmp_seq[7:0] <= gmii_rxd;//判断ICMP报文类型是否是回显请求if(icmp_type == ECHO_REQUEST)beginstate_en <= 1'b1;cnt <= 5'd0;endelse beginerror_flag <= 1'b1;cnt <= 5'd0;endendelse;                    endelse;endRX_DATA:beginif(gmii_rx_dv)beginrec_en_cnt <= rec_en_cnt + 1'b1;rec_en <= 1'b1;icmp_rx_cnt <= icmp_rx_cnt + 1'b1;rec_data <= gmii_rxd;//判断接收数据的奇偶个数if(icmp_rx_cnt == icmp_data_length - 1'b1)beginicmp_rx_data_d0 <= 8'd0;if(icmp_data_length[0])//判断接收数据个数是否为奇数reply_checksum_add <= {8'd0,gmii_rxd} + reply_checksum_add;else reply_checksum_add <= {icmp_rx_data_d0,gmii_rxd} + reply_checksum_add;endelse if(icmp_rx_cnt < icmp_data_length)beginicmp_rx_data_d0 <= gmii_rxd;icmp_rx_cnt <= icmp_rx_cnt + 1'b1;if(icmp_rx_cnt[0] == 1'b1)reply_checksum_add <= {icmp_rx_data_d0,gmii_rxd} + reply_checksum_add;elsereply_checksum_add <= reply_checksum_add;endelse;if(icmp_rx_cnt == icmp_data_length - 1'b1)beginstate_en <= 1'b1;icmp_rx_cnt <= 16'd0;rec_en_cnt <= 2'd0;rec_pkt_done <= 1'b1;rec_byte_num <= icmp_data_length;endelse;endelse;endRX_DONE:begin   //单包数据接收完成rec_en <= 1'b0;if(gmii_rx_dv == 1'b0 && state_en == 1'b0)beginstate_en <= 1'b1;reply_checksum <= reply_checksum_add;reply_checksum_add <= 32'd0;endelse;enddefault: ;endcaseendendendmodule

仿真接如下:

接收部分和ARP大同小异故不再赘述。

4.上板测试:

IP协议和ICMP协议 - eiSouthBoy - 博客园 (cnblogs.com)

IP协议及ICMP协议简介_ip与icmp-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/57683.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

将 el-date-picker获取的时间数据转换成时间戳

在Vue.js中使用Element UI的el-date-picker组件时&#xff0c;你可以获取用户选择的日期并将其转换为时间戳。el-date-picker通常返回的是一个Date对象或一个格式化后的字符串&#xff08;取决于你如何配置它&#xff09;。下面是一个示例&#xff0c;展示了如何将el-date-pick…

Spring Cloud OAuth认证中心

在微服务架构中&#xff0c;由于不同的业务会拆分成不同的微服务&#xff0c;传统的单体项目一般是通过过滤器进行拦截校验&#xff0c;而微服务显然不可能分发到各个服务进行用户认证&#xff0c;这就需要由一个统一的地方来管理所有服务的认证信息&#xff0c;实现只登录一次…

松脂醇-落叶松脂素还原酶(pinoresinol-lariciresinol reductase, PLR)克隆与鉴定-文献精读71

菘蓝中松脂醇-落叶松脂素还原酶编码基因IiPLR2的克隆与功能分析 摘要 松脂醇-落叶松脂素还原酶(pinoresinol-lariciresinol reductase, PLR)是植物中木脂素生物合成的关键酶&#xff0c;能连续催化两步反应分别生成落叶松脂素和开环异落叶松脂素。落叶松脂素等木脂素类成分是…

Unity SpriteEditor 中的图集处理功能

本文仅作笔记学习和分享&#xff0c;不用做任何商业用途 本文包括但不限于unity官方手册&#xff0c;unity唐老狮等教程知识&#xff0c;如有不足还请斧正 因为unity不只能做3d&#xff0c;还有2d游戏需要大量编辑处理图片素材&#xff0c;所以需要了解Sprite&#xff08;精灵&…

KPaaS 集成平台低代码在跨境电商行业的应用

在全球化的经济浪潮中&#xff0c;跨境电商行业蓬勃发展&#xff0c;机遇与挑战并存。随着业务规模的不断扩大和市场竞争的日益激烈&#xff0c;跨境电商企业面临着诸多复杂的业务管理和技术难题。KPaaS 业务集成扩展平台以其低代码的创新特性&#xff0c;为跨境电商行业带来了…

头歌——人工智能(机器学习 --- 决策树2)

文章目录 第5关&#xff1a;基尼系数代码 第6关&#xff1a;预剪枝与后剪枝代码 第7关&#xff1a;鸢尾花识别代码 第5关&#xff1a;基尼系数 基尼系数 在ID3算法中我们使用了信息增益来选择特征&#xff0c;信息增益大的优先选择。在C4.5算法中&#xff0c;采用了信息增益率…

银河麒麟相关

最近安装了银河麒麟server版本&#xff0c;整理下遇到的一些小问题 1、vmware安装Kylin-Server-V10-SP3-General-Release-2303-X86_64虚拟机完成后&#xff0c;桌面窗口很小&#xff0c;安装vmwaretools后解决&#xff0c;下载地址http://softwareupdate.vmware.com/cds/vmw-de…

leetcode-71-简化路径

题解&#xff1a; 1、以"/"作为分隔符对字符串进行分割得到数组names; 2、初始化一个栈stack&#xff08;python中的栈使用列表实现&#xff09;&#xff1b; 3、遍历数组names&#xff1b;如果当前元素为".."且栈不为空&#xff0c;则将弹出栈顶元素&a…

网络安全行业10大副业汇总,总有一个适合你

网络安全行业这10大副业汇总 总有一个适合你 引言 在当今的网络安全行业中&#xff0c;除了全职工作外&#xff0c;许多师傅还通过副业来增加收入、不断拓展自身技能&#xff0c;并积累更多实际操作经验&#xff0c;为职业发展增添了无限可能。 本文提供了10种适合各种类型…

Android13关于获取外部存储文件的相关问题及解决方案记录

Android的学习路上... 测试设备&#xff1a;vivo X90s安卓版本&#xff1a; Android13开发环境&#xff1a;AndroidStudio FlamingoSDK&#xff1a;33 最近我在Android13的环境下尝试写一个文件选择器&#xff0c;以便日后的开发使用。但是我们知道&#xff0c;从Android13 (A…

django restful API

文章目录 项目地址一、django环境安装以及初识restful1.1 安装python 3.10的虚拟环境1.2 创建django工程文件1.3 创建一个book app1.4 序列化(Django JsonResponse)1.4.1创建一个Models1.4.2 创建django的超级用户admin1.4.3 添加serializers.py生成序列化器1.5 FBV创建视图1…

用docker Desktop 下载使用thingsboard/tb-gateway

1、因为正常的docker pull thingsboard/tb-gateway 国内不行了&#xff0c;所以需要其它工具来下载 2、在win下用powershell管理员下运行 docker search thingsboard/tb-gateway 可以访问到了 docker pull thingsboard/tb-gateway就可以下载了 3、docker Desktop就可以看到…

ComfyUI初体验

ComfyUI 我就不过多介绍了&#xff0c;安装和基础使用可以看下面大佬的视频&#xff0c;感觉自己靠图文描述的效果不一定好&#xff0c;大家看视频比较方便。 ComfyUI全球爆红&#xff0c;AI绘画进入“工作流时代”&#xff1f;做最好懂的Comfy UI入门教程&#xff1a;Stable D…

分布式ID生成策略

文章目录 分布式ID必要性1.UUID2.基于DB的自增主键方案3.数据库多主模式4.号段模式5.Redis6.Zookeeper7.ETCD8.雪花算法9.百度(Uidgenerator)10.美团(Leaf)11.滴滴(TinyID) 分布式ID必要性 业务量小于500W的时候单独一个mysql即可提供服务&#xff0c;再大点的时候就进行读写分…

nuScenes数据集使用的相机的外参和内参

因为需要用不同数据集测试对比效果&#xff0c;而一般的模型代码里实现的检测结果可视化都是使用open3d的Visualizer在点云上画的3d框&#xff0c;展示出来的可视化效果很差&#xff0c;可能是偷懒&#xff0c;没有实现将检测结果投影到各相机的图像上&#xff0c;所以检测效果…

[ARM-2D 专题]4. 快速搭建ARM2D的PC仿真开发环境及避坑手法

有几种情况你需要使用pc仿真开发环境&#xff1a; 手上没有合适的硬件条件只想快速的了解一下ARM-2D开发过程中&#xff0c;加速开发过程&#xff0c;避免频繁的下载代码 无论如何&#xff0c;pc仿真开发环境&#xff0c;你都值得拥有。 第一步&#xff0c;先下载源代码&#…

基于ElementPlus的Form组件封装

前言 我们在项目开发过程中遇到最多就是表单页面的开发&#xff0c;那么使用频率比较高的就是Form组件&#xff0c;无论是vue亦或者是react&#xff0c;我们在项目中使用到UI库都会有Form组件。多数情况下都是用到了Form组件&#xff0c;我们先根据UI库或者其他类似的页面直接…

h5页面与小程序页面互相跳转

小程序跳转h5页面 一个home页 /pages/home/home 一个含有点击事件的元素&#xff1a;<button type"primary" bind:tap"toWebView">点击跳转h5页面</button>toWebView(){ wx.navigateTo({ url: /pages/webview/webview }) } 一个webView页 /pa…

物联网行业应用实训室建设方案

一、建设背景 随着物联网技术的迅猛发展和广泛应用&#xff0c;物联网产业已跃升为新时代的经济增长引擎&#xff0c;对于产业升级和社会信息化水平的提升具有举足轻重的地位。因此&#xff0c;为了满足这一领域的迫切需求&#xff0c;培养具备物联网技术应用能力的优秀人才成…

自动发现-实现运维管理自动化

nVisual-Discovery是一款自动化工具软件&#xff0c;通过多种自动发现技术&#xff0c;协助运维管理人员快速建立可视化的网络文档&#xff0c;提升网络管理的效率与准确性。 01 IP扫描发现 当我们新接手一个网络运维项目&#xff0c;通常缺乏精准的网络文档数据&#xff0c;…