之前简单介绍并实现了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 / 1ps
module 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) begin
if(!rst_n)begin
tx_start_r0 <= 1'b0;
tx_start_r1 <= 1'b0;
tx_start_r2 <= 1'b0;
end
else begin
tx_start_r0 <= tx_start;
tx_start_r1 <= tx_start_r0;
tx_start_r2 <= tx_start_r1;
end
end
//寄存数据有效字节
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
tx_data_num <= 16'd0;
total_num <= 16'd0;
end
else if(posedge_tx_start && cur_state == IDLE)begin
tx_data_num <= tx_byte_num;
total_num <= tx_byte_num + 16'd28;
end
else;
end
//触发发送信号
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
trig_tx_en <= 1'b0;
end
else begin
trig_tx_en <= posedge_tx_start;
end
end
//三段式状态机第一段
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
cur_state <= IDLE;
else
cur_state <= next_state;
end
//三段式状态机第二段
always @(*) begin
next_state = IDLE;
case (cur_state)
IDLE: begin
if(state_en)
next_state = CHECK_SUM;
else
next_state = IDLE;
end
CHECK_SUM: begin
if(state_en)
next_state = CHECK_ICMP;
else
next_state = CHECK_SUM;
end
CHECK_ICMP: begin
if(state_en)
next_state = PREAMBLE;
else
next_state = CHECK_ICMP;
end
PREAMBLE: begin
if(state_en)
next_state = ETH_HEAD;
else
next_state = PREAMBLE;
end
ETH_HEAD: begin
if(state_en)
next_state = IP_HEAD;
else
next_state = ETH_HEAD;
end
IP_HEAD: begin
if(state_en)
next_state = TX_DATA;
else
next_state = IP_HEAD;
end
TX_DATA: begin
if(state_en)
next_state = CRC;
else
next_state = TX_DATA;
end
CRC: begin
if(state_en)
next_state = IDLE;
else
next_state = CRC;
end
default: ;
endcase
end
//三段式状态机第三段
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
state_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];
end
else begin
state_en <= 1'b0;
crc_en <= 1'b0;
gmii_tx_en <= 1'b0;
tx_done_t <= 1'b0;
case (next_state)
IDLE: begin
if(trig_tx_en)begin
state_en <= 1'b1;
//版本号:4 首部长度:5
ip_head[0] <= {8'h45,8'h00,total_num};
//16位标识,每次发送累加1
ip_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代表UDP
ip_head[2] <= {8'h80,8'h01,16'h0000};
ip_head[3] <= BOARD_IP;
//目的ip地址
if(des_ip != 32'd0)
ip_head[4] <= des_ip;
else
ip_head[4] <= DES_IP;
//8位 icmp type,8位 icmp code
ip_head[5][31:16] <= {ECHO_REPLY,8'h00};
//16位标识符 16位序列号
ip_head[6] <= {icmp_id,icmp_seq};
//更新MAC地址
if(des_ip != 48'd0)begin
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];
end
else;
end
else;
end
CHECK_SUM: begin
cnt <= cnt + 1'b1;
if(cnt == 5'd0)begin
check_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];
end
else if(cnt == 5'd1)begin//可能出现进位,高16位、低16位进行相加
check_buffer <= check_buffer[31:16] + check_buffer[15:0];
end
else if(cnt == 5'd2)begin//可能再次出现进位,再次相加
check_buffer <= check_buffer[31:16] + check_buffer[15:0];
end
else if(cnt == 5'd3)begin
cnt <= 5'd0;
state_en <= 1'b1;
ip_head[2][15:0] <= ~check_buffer[15:0];//按位取反
end
else;
end
CHECK_ICMP: begin//ICMP首部+数据校验
cnt <= cnt + 1'b1;
if(cnt == 5'd0)begin
check_buffer_icmp <= ip_head[5][31:16] + ip_head[6][31:16]
+ip_head[6][15:0] + reply_checksum;
end
else if(cnt == 5'd1)begin
check_buffer_icmp <= check_buffer_icmp[31:16] + check_buffer_icmp[15:0];
end
else if(cnt == 5'd2)begin
check_buffer_icmp <= check_buffer_icmp[31:16] + check_buffer_icmp[15:0];
end
if(cnt == 5'd3)begin
cnt <= 5'd0;
state_en <= 1'b1;
//ICMP 16位校验和
ip_head[5][15:0] <= ~check_buffer_icmp[15:0];
end
else;
end
PREAMBLE: begin
gmii_tx_en <= 1'b1;
gmii_txd <= preamble[cnt];
if(cnt == 5'd7)begin
cnt <= 5'd0;
state_en <= 1'b1;
end
else
cnt <= cnt + 1'b1;
end
ETH_HEAD:begin
gmii_tx_en <= 1'b1;
gmii_txd <= eth_head[cnt];
crc_en <= 1'b1;
if(cnt == 5'd13)begin
cnt <= 5'd0;
state_en <= 1'b1;
end
else
cnt <= cnt + 1'b1;
end
IP_HEAD: begin
crc_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)begin
gmii_txd <= ip_head[cnt][15:8];
if(cnt == 5'd6)
tx_req <= 1'b1;//提前拉高发送请求数据,数据有效就将数据发送
else;
end
else if(tx_bit_sel == 2'd3)begin
gmii_txd <= ip_head[cnt][7:0];
if(cnt == 5'd6)begin
cnt <= 5'd0;
state_en <= 1'b1;
end
else
cnt <= cnt + 1'b1;
end
else;
end
TX_DATA: begin
crc_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)begin
data_cnt <= data_cnt + 1'b1;
end
else if(data_cnt == tx_data_num - 1'b1)begin
if(data_cnt + real_add_cnt < real_tx_data_num - 1'b1)
real_add_cnt <= real_add_cnt + 1'b1;
else begin
state_en <= 1'b1;
data_cnt <= 16'd0;
real_add_cnt <= 'd0;
end
end
else;
if(data_cnt == tx_data_num - 16'd2)
tx_req <= 1'b0;
else;
end
CRC:begin
gmii_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) begin
gmii_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]};
end
else if(tx_bit_sel == 3'd3) begin
gmii_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;
end
else;
end
default:;
endcase
end
end
//发送完成信号及crc复位信号
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
tx_done <= 1'b0;
crc_clr <= 1'b0;
end
else begin
tx_done <= tx_done_t;
crc_clr <= tx_done_t;
end
end
endmodule
仿真结果如下所示:
检测到开始发送信号上升沿,触发信号拉高,进入校验和检测状态。
累加结果。
发送以太网帧头部分不再赘述,因为之前就已经详细说过,重点关注ip首部发送过程。需要注意的是ip首部总共包含20字节数据,并且一定要搞清楚每一部分的含义是啥。加上ICMP首部总共是28字节,发送完成之后再发送20字节数据,最后进入CRC状态。
3.2接收部分:
`timescale 1ns / 1ps
module 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) begin
if(!rst_n)
cur_state <= IDLE;
else
cur_state <= next_state;
end
//三段式状态机第二段
always @(*) begin
next_state = IDLE;
case (cur_state)
IDLE: begin
if(state_en)
next_state = PREAMBLE;
else if(error_flag)
next_state = RX_DONE;
else
next_state = IDLE;
end
PREAMBLE:begin
if(state_en)
next_state = ETH_HEAD;
else if(error_flag)
next_state = RX_DONE;
else
next_state = PREAMBLE;
end
ETH_HEAD:begin
if(state_en)
next_state = IP_HEAD;
else if(error_flag)
next_state = RX_DONE;
else
next_state = ETH_HEAD;
end
IP_HEAD:begin
if(state_en)
next_state = ICMP_HEAD;
else if(error_flag)
next_state = RX_DONE;
else
next_state = IP_HEAD;
end
ICMP_HEAD:begin
if(state_en)
next_state = RX_DATA;
else if(error_flag)
next_state = RX_DONE;
else
next_state = ICMP_HEAD;
end
RX_DATA:begin
if(state_en)
next_state = RX_DONE;
else
next_state = RX_DATA;
end
RX_DONE:begin
if(state_en)
next_state = IDLE;
else
next_state = RX_DONE;
end
default:next_state = IDLE;
endcase
end
//三段式状态机第三段
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
state_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;
end
else begin
state_en <= 1'b0;
error_flag <= 1'b0;
rec_pkt_done <= 1'b0;
case (next_state)
IDLE:begin
if((gmii_rx_dv) && gmii_rxd == 8'h55)
state_en <= 1'b1;
else;
end
PREAMBLE:begin
if(cnt < 5'd6 && gmii_rxd != 8'h55)
error_flag <= 1'b1;
else if(cnt == 5'd6)begin
cnt <= 5'd0;
if(gmii_rxd == 8'hd5)
state_en <= 1'b1;
else
error_flag <= 1'b1;
end
else
cnt <= cnt + 1'b1;
end
ETH_HEAD:begin
if(gmii_rx_dv)begin
cnt <= 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)begin
eth_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;
else
error_flag <= 1'b1;
end
else;
end
else;
end
IP_HEAD:begin
if(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)begin
icmp_data_length <= total_length - 16'd28;//ip帧头20字节+ICMP帧头8字节
end
else if(cnt == 5'd9)begin
//如果当前接收协议不是ICMP协议则停止解析协议
if(gmii_rxd != ICMP_TYPE)begin
error_flag <= 1'b1;
cnt <= 5'd0;
end
else;
end
else if((cnt >= 5'd16) && (cnt <= 5'd18))
des_ip <= {des_ip[23:0],gmii_rxd};
else if(cnt == 5'd19)begin
des_ip <= {des_ip[23:0],gmii_rxd};
if((des_ip[23:0] == BOARD_IP[31:8]) && gmii_rxd <= BOARD_IP[7:0])begin
state_en <= 1'b1;
cnt <= 5'd0;
end
else begin
//ip不是开发板地址
error_flag <= 1'b1;
cnt <= 5'd0;
end
end
else;
end
else;
end
ICMP_HEAD:begin
if(gmii_rx_dv)begin
cnt <= 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)begin
icmp_seq[7:0] <= gmii_rxd;
//判断ICMP报文类型是否是回显请求
if(icmp_type == ECHO_REQUEST)begin
state_en <= 1'b1;
cnt <= 5'd0;
end
else begin
error_flag <= 1'b1;
cnt <= 5'd0;
end
end
else;
end
else;
end
RX_DATA:begin
if(gmii_rx_dv)begin
rec_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)begin
icmp_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;
end
else if(icmp_rx_cnt < icmp_data_length)begin
icmp_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;
else
reply_checksum_add <= reply_checksum_add;
end
else;
if(icmp_rx_cnt == icmp_data_length - 1'b1)begin
state_en <= 1'b1;
icmp_rx_cnt <= 16'd0;
rec_en_cnt <= 2'd0;
rec_pkt_done <= 1'b1;
rec_byte_num <= icmp_data_length;
end
else;
end
else;
end
RX_DONE:begin //单包数据接收完成
rec_en <= 1'b0;
if(gmii_rx_dv == 1'b0 && state_en == 1'b0)begin
state_en <= 1'b1;
reply_checksum <= reply_checksum_add;
reply_checksum_add <= 32'd0;
end
else;
end
default: ;
endcase
end
end
endmodule
仿真接如下:
接收部分和ARP大同小异故不再赘述。