基于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.rhkb.cn/news/453357.html

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

相关文章

第13篇:无线与移动网络安全

目录 引言 13.1 无线网络的安全威胁 13.2 无线局域网的安全协议 13.3 移动通信中的安全机制 13.4 蓝牙和其他无线技术的安全问题 13.5 无线网络安全的最佳实践 13.6 总结 第13篇&#xff1a;无线与移动网络安全 引言 无线和移动网络的发展为我们的生活带来了极大的便利…

边缘计算与联邦学习:探索隐私保护和高效数据处理的结合

个人主页&#xff1a;chian-ocean 文章专栏 边缘计算与联邦学习&#xff1a;探索隐私保护和高效数据处理的结合 1. 引言 随着物联网(IoT)设备的普及&#xff0c;网络边缘产生了大量数据。将这些数据上传至云端进行集中式计算和处理&#xff0c;既有隐私泄露的风险&#xff…

15分钟学Go 实战项目一:命令行工具

实战项目一&#xff1a;命令行工具 1. 引言 命令行工具是开发者常用的工具之一&#xff0c;它可以帮助用户通过命令行界面对程序进行控制和交互。在这节中&#xff0c;我们将创建一个简单的命令行工具&#xff0c;以帮助你理解Go语言的基本语法和如何处理命令行输入。在这个过…

详解安卓和IOS的唤起APP的机制,包括第三方平台的唤起方法比如微信

网页唤起APP是一种常见的跨平台交互方式&#xff0c;它允许用户从网页直接跳转到移动应用程序。 这种技术广泛应用于各种场景&#xff0c;比如让用户在浏览器中点击链接后直接打开某个应用&#xff0c;或者从网页引导用户下载安装应用。实现这一功能主要依赖于URL Scheme、Univ…

ESP32-S3学习笔记:分区表(Partition Table)的二进制分析

一、参考资料 用于研究的官方示例代码&#xff1a;esp-idf-v5.3\examples\storage\partition_api\partition_find参考的官方文档&#xff1a;ESP-IDF编程指南&#xff1a;分区表 二、准备工作 用VS Code打开示例代码&#xff0c;打开示例代码的CSV自定义分区表&#xff0c;如…

大数据实验3: HDFS基础编程

实验3&#xff1a; HDFS基础编程 一、实验目的 HDFS的shell命令使用HDFS的JAVA API使用&#xff1b; 二、实验平台 操作系统&#xff1a;Linux&#xff08;Ubuntu16.04&#xff09;&#xff1b;Hadoop版本&#xff1a;3.3.1&#xff1b;JDK版本&#xff1a;1.8&#xff1b;…

498.对角线遍历

目录 题目解法代码说明&#xff1a;输出&#xff1a; 如何确定起始点&#xff1f;解释一下max(0,d−m1)是什么意思&#xff1f; 如何遍历对角线&#xff1f;.push_back是怎么用的&#xff1f; 题目 给你一个大小为 m x n 的矩阵 mat &#xff0c;请以对角线遍历的顺序&#xf…

Java知识巩固(七)

目录 面向对象 面向对象三大特征 封装 继承 多态 多态 深拷贝和浅拷贝区别了解吗?什么是引用拷贝? 浅拷贝 深拷贝 面向对象 万物皆为对象&#xff0c;也就是描述某个事物解决问题的过程中所发生的事情。 面向对象三大特征 封装 封装是指把一个对象的状态信息&…

目前最新 Reflector V11.1.0.2067版本 .NET 反编译软件

目前最新 Reflector V11.1.0.2067版本 .NET 反编译软件 一、简介二、.NET Reflector的主要功能包括&#xff1a;1. **反编译**: 反编译是将已编译的.NET程序集&#xff08;如.dll或.exe文件&#xff09;转换回可读的源代码。这使得开发者可以查看和学习第三方库的实现细节&…

C++ string(2)

文章目录 1.初识迭代器和范围for1.1迭代器1.2范围for1.3 aout关键字 2.字符串长度相关计算1.size 和 length2. capacity 和 reserve 3.例题演示1. [917. 仅仅反转字母 - 力扣&#xff08;LeetCode&#xff09;](https://leetcode.cn/problems/reverse-only-letters/description…

spring day 1021

ok了家人们&#xff0c;这周学习spring框架&#xff0c;我们一起去看看吧 Spring 一.Spring概述 1.1 Spring介绍 官网&#xff1a; https://spring.io/ 广义的 Spring &#xff1a; Spring 技术栈 &#xff08;全家桶&#xff09; 广义上的 Spring 泛指以 Spring Framework…

Spring AI 整体介绍_关键组件快速入门_prompt_embedding等

Spring AI&#xff1a;Java开发者的AI集成新利器 在过去&#xff0c;Java开发者在构建AI应用时面临着缺乏统一框架的问题&#xff0c;导致不同AI服务的集成过程复杂且耗时。Spring AI应运而生&#xff0c;旨在为基于Java的应用程序提供一个标准化、高效且易于使用的AI开发平台…

浅说差分算法(下)

我们上节课学了一维的差分&#xff0c;但其实还有二维差分&#xff0c;只是比较难写。 差分 二维差分的定义 二维差分是指对于一个n*m的矩阵a&#xff0c;要求支持操作pro(x1,y1,x2,y2,a)&#xff0c;表示对于以(x1,y1)为左上角&#xff0c;(x2,y2)为右下角的矩形区域&#…

生产车间质量管理有什么用?怎么做?

在生产车间的质量管理中&#xff0c;科学有效的管理方法和严格规范的执行流程是至关重要的&#xff0c;它能够帮助企业提高产品质量、降低次品率、确保生产过程的稳定性和效率。然而&#xff0c;许多企业在生产车间质量管理方面存在诸多问题&#xff0c;常常会面临以下困境&…

多微批量自动加好友

在数字化时代&#xff0c;微信不仅是社交通讯的工具&#xff0c;更是一个拥有庞大用户基础的流量平台。对于企业而言&#xff0c;微信是打造私域流量池的理想选择之一。然而&#xff0c;随着微信号的增多&#xff0c;手动添加好友和备注变得既繁琐又耗时。幸运的是&#xff0c;…

UNI VFX Missiles Explosions for Visual Effect Graph

Unity URP和HDRP的通用视觉效果 使用在视觉效果图中制作的高性能GPU粒子系统。 无需进入视觉效果图编辑器即可轻松自定义VFX。 使用(VFX)事件——一个游戏对象可存储多个效果,这些效果可通过C#或视觉脚本触发。 总共32个事件(不包括“停止”事件)。 ❓ 什么是(VFX)事件?…

Cpp::STL—容器适配器Stack和Queue的讲解和模拟实现(15)

文章目录 前言一、适配器模式概念分类 二、Stack核心作用代码实现 三、Queue核心作用代码实现 四、deque双端队列貌似兼收并蓄&#xff1f;实则也难以兼得~ 总结 前言 适配器也是STL六大组件之一&#xff0c;请跟我一起领悟它的智慧&#xff01;   正文开始&#xff01; 一、…

consumer 角度讲一下i2c外设

往期内容 I2C子系统专栏&#xff1a; I2C&#xff08;IIC&#xff09;协议讲解-CSDN博客SMBus 协议详解-CSDN博客I2C相关结构体讲解:i2c_adapter、i2c_algorithm、i2c_msg-CSDN博客内核提供的通用I2C设备驱动I2c-dev.c分析&#xff1a;注册篇内核提供的通用I2C设备驱动I2C-dev.…

浅析建造者模式

建造者模式 一、基础知识介绍 1. 问题引出 上图面存在的问题&#xff1a;产品和产品创建的过程是封装在一起的。耦合性太强 解决方法: 将二者解耦和 2.建造者模式介绍 将复杂对象的构造过程抽象出来&#xff0c;用户不用知晓里面的构建细节 3.四个角色 建造者模式的四个角…

Java项目-基于springboot框架的财务管理系统项目实战(附源码+文档)

作者&#xff1a;计算机学长阿伟 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、ElementUI等&#xff0c;“文末源码”。 开发运行环境 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBoot、Vue、Mybaits Plus、ELementUI工具&#xff1a;IDEA/…