之前简单介绍并实现了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字段的数值。其中ICMP是1,TCP是6,UDP是17。
头部校验和(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数据报格式如下图:
-
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) &ð_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博客