以太网ICMP协议(ping指令)——FPGA学习笔记25
--素材来源原子哥
一、IP协议
1、IP简介
IP是Internet Protocol(网际互连协议)的缩写。IP 协议是 TCP/IP 协议簇中的核心协议,它为上层协议提供无状态、无连接、不可靠的服务。IP 协议规定了数据传输时的基本单元和格式 。
IP协议是 OSI 参考模型中网络层的重要成员,与 ICMP协议及 IGMP 协议共同构成OSI 参考模型模型中的网络层 。
2、OSI参考模型
3、IP协议格式
4、IP包详细格式
前 20 个字节和紧跟其后的可选字段是 IP 数据报的首部,前 20 个字节是固定的,后面可选字段是可有可无的,首部的每一行以 32 位(4 个字节)为单位。每个字节传输规则为由比特最高位到最低位的顺序逐一进行发送, 4 字节的 32bit 值按照以下次序传输:首先 7-0bit,其次 15-8 bit,然后 23-16bit,最后是 31-24bit。
版本: 4 位 IP 版本号(Version),这个值设置为二进制的 0100 时表示 IPv4,设置为 0110 时表示 IPv6,目前使用比较多的 IP 协议版本号是 4。
首部长度: 4 位首部长度(IHL, Internet Header Length),表示 IP 首部一共有多少个 32 位(4 个字节) 。在没有可选字段时, IP 首部长度为 20 个字节, 因此首部长度的值为 5。
服务类型: 8 位服务类型(TOS, Type of service),该字段被划分成两个子字段: 3 位优先级字段(现在已经基本忽略掉了)和 4 位 TOS 字段,最后一位固定为 0。服务类型为 0 时表示一般服务。
总长度: 16 位 IP 数据报总长度(Total Length),包括 IP 首部和 IP 数据部分,以字节为单位。我们利用 IP 首部长度和 IP 数据报总长度,就可以知道 IP 数据报中数据内容的起始位置和长度。由于该字段长16bit,所以 IP 数据报最长可达 65535 字节。尽管理论上可以传输长达 65535 字节的 IP 数据报,但实际上还要考虑网络的最大承载能力等因素。
标识字段: 16 位标识(Identification)字段,用来标识主机发送的每一份数据报。通常每发送一份报文它的值就会加 1。
标志字段: 3 位标志(Flags)字段,第 1 位为保留位;第 2 位表示禁止分片(1 表示不分片 0:允许分片);第 3 位标识更多分片(除了数据报的最后一个分片外,其它分片都为 1)。
片偏移: 13 位片偏移(Fragment Offset),在接收方进行数据报重组时用来标识分片的顺序。
生存时间: 8 位生存时间字段, TTL(Time To Live)域防止丢失的数据包在无休止的传播,一般被设置为 64 或者 128。
协议: 8 位协议(Protocol)类型,表示此数据报所携带上层数据使用的协议类型, ICMP 为 1, TCP 为6, UDP 为 17。
首部校验和: 16 位首部校验和(Header Checksum),该字段只校验数据报的首部,不包含数据部分;校验 IP 数据报头部是否被破坏、篡改和丢失等。
源 IP 地址: 32 位源 IP 地址(Source Address),即发送端的 IP 地址, 如 192.168.1.123。
目的 IP 地址: 32 位目的 IP 地址(Destination Address),即接收端的 IP 地址, 如 192.168.1.102。
可选字段: 是数据报中的一个可变长度的可选信息,选项字段以 32bit 为界,不足时插入值为 0 的填充字节,保证 IP 首部始终是 32bit 的整数倍(一般为0)
5、IP首部校验和
0x4500 + 0x003C + 0x0000 + 0x4000 + 0x4001 + 0x0000(计算时强制置0) + 0xc0a8
+ 0x010a + 0xc0a8 + 0x0166 = 0x248FD
进位拿出来与低位相加。
0x0002 + 0x48FD = 0x000048FF(此种情况并未出现进位)
0x0000+ 0x48FF= 0x48FF(此种情况并未出现进位)
check_sum = ~0x48FF(按位取反)= 0xb700
对各个单元采用反码加法运算(即高位溢出位会加到低位,通常的补码运算是直接丢掉溢出的高位)
二、ICMP协议
1、简介
ICMP是Internet Control Message Protocol的缩写,即互联网控制消息协议。它用于TCP/IP网络中发送控制消息,提供可能发生在通信环境中的各种问题反馈,通过这些信息,使网络管理者可以对所发生的问题作出诊断,然后采取适当的措施解决问题。
2、ICMP协议包格式
(1)查询报文
常用类型有:类型 0,代码 0:表示回显应答(ping 应答)。 类型 8,代码 0:表示回显请求(ping 请求)。 类型 11,代码 0:超时; 类型 3,代码 0:网络不可达; 类型 3,代码 1:主机不可达; 类型 5,代码 0:重定向。
代码(code):占用了 8 bit 位,根据 ICMP 差错报文的类型,进一步分析错误的原因,代码值不同对应的错误也不同,例如:类型为 11 且代码为 0,表示数据传输过程中超时了,超时的具体原因是 TTL 值为 0,数据报被丢弃。
校验和(checksum):占用了 16 bit 位, 校验的方法同上述 IP 首部校验和的方法一致。 数据发送到目的地后需要对 ICMP 数据报文做一个校验,用于检查数据报文是否有错误。
标识符(Identifier):占用了 16 bit 位,对于每一个发送的数据报进行标识。
序列号(Sequence number):占用了 16 bit 位,对于发送的每一个数据报文进行编号,比如:发送的第一个数据报序列号为 1,第二个序列号为 2。
数据(Data):要发送的 ICMP 数据。
3、常用ICMP报文类型
三、程序设计
目标:电脑ping开发板实现开发板应答
1、整体框图:
2、ICMP编写:
(1)icmp_rx
前导码+帧起始界定符->以太网帧头->IP 首部->ICMP 首->ICMP 数据(有效数据) ->接收结束
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2024/12/26 15:41:37
// Design Name:
// Module Name: icmp_rx
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module icmp_rx(
input clk ,
input rst_n ,
input gmii_rx_dv ,
input [ 7:0] gmii_rxd ,
output reg rec_pkt_done , //以太网单包数据接收完成信号
output reg rec_en , //以太网接收数据使能信号 ---fifo_en
output reg [ 7:0] rec_data , //以太网接收数据 ---fifo_data
output reg [15:0] rec_byte_num , //以太网接收有效字数 bit
output reg [15:0] icmp_id , //ICMP标识符
output reg [15:0] icmp_seq , //ICMP序列号
output reg [31:0] reply_checksum //接收数据校验
);
//开发板MAC地址 00-11-22-33-44-55
parameter BOARD_MAC = 48'h00_11_22_33_44_55;
//开发板IP地址 192.168.1.10
parameter BOARD_IP = {8'd192,8'd168,8'd1,8'd10};
//状态机状态定义
localparam st_idle = 7'b000_0001 ; //初始状态,等待接收前导码
localparam st_preamble = 7'b000_0010 ; //接收前导码状态
localparam st_eth_head = 7'b000_0100 ; //接收以太网帧头
localparam st_ip_head = 7'b000_1000 ; //接收IP首部
localparam st_icmp_head = 7'b001_0000 ; //接收ICMP首部
localparam st_rx_data = 7'b010_0000 ; //接收有效数据
localparam st_rx_end = 7'b100_0000 ; //接收结束
//以太网类型定义
localparam ETH_TYPE = 16'h0800 ; //以太网协议类型 IP协议
localparam ICMP_TYPE = 8'd1 ; //ICMP协议类型
//ICMP报文类型:回显请求
localparam ECHO_REQUEST = 8'h08 ;
reg [6 :0] cur_state ; //当前状态
reg [6 :0] next_state ; //下一状态
reg skip_en ; //状态跳转信号
reg error_en ; //状态错误信号
reg [4 :0] cnt ; //状态数据计数器
reg [47:0] des_mac ; //目的MAC
reg [15:0] eth_type ; //以太网类型
reg [5 :0] ip_head_byte_num ; //IP首部长度
reg [15:0] total_length ; //接收数据字节长度
reg [15:0] icmp_data_length ; //有效数据长度
reg [31:0] des_ip ; //目的IP
reg [7 :0] icmp_type ; //ICMP报文类型:用于标识错误类型的差错报文或者查询类型的报告报文
reg [7 :0] icmp_code ; //ICMP报文代码:根据ICMP差错报文的类型,进一步分析错误的原因,代码值不同对应的错误也不同
//例如:类型为11且代码为0,表示数据传输过程中超时了,超时的具体原因是TTL值为0,数据报被丢弃。
reg [15:0] icmp_checksum ; //接收校验和:数据发送到目的地后需要对ICMP数据报文做一个校验,用于检查数据报文是否有错误
reg [1 :0] rec_en_cnt ; //8bit转32bit计数器
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) begin
cur_state <= st_idle ;
end
else begin
cur_state <= next_state ;
end
end
//
always @(*) begin
if (!rst_n) begin
next_state = st_idle ;
end
else begin
case (cur_state)
st_idle :begin //等待接收前导码
if (skip_en) begin
next_state = st_preamble ;
end
else begin
next_state = st_idle ;
end
end
st_preamble :begin //接收前导码
if (skip_en ) begin
next_state = st_eth_head ;
end
else if(error_en)begin
next_state = st_rx_end ;
end
else begin
next_state = st_preamble ;
end
end
st_eth_head :begin //接收以太网帧头
if (skip_en ) begin
next_state = st_ip_head ;
end
else if(error_en)begin
next_state = st_rx_end ;
end
else begin
next_state = st_eth_head ;
end
end
st_ip_head :begin //接收IP首部
if (skip_en ) begin
next_state = st_icmp_head;
end
else if(error_en)begin
next_state = st_rx_end ;
end
else begin
next_state = st_ip_head ;
end
end
st_icmp_head :begin //接收ICMP首部
if (skip_en ) begin
next_state = st_rx_data ;
end
else if(error_en)begin
next_state = st_rx_end ;
end
else begin
next_state = st_icmp_head;
end
end
st_rx_data :begin //接收有效数据
if (skip_en ) begin
next_state = st_rx_end ;
end
else begin
next_state = st_rx_data ;
end
end
st_rx_end :begin //接收结束
if (skip_en ) begin
next_state = st_idle ;
end
else begin
next_state = st_rx_end ;
end
end
default: next_state = st_idle ;
endcase
end
end
//
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
skip_en <= 1'b0 ;
error_en <= 1'b0 ;
cnt <= 5'd0 ;
des_mac <= 48'd0 ;
eth_type <= 16'd0 ;
des_ip <= 32'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
skip_en <= 1'b0;
error_en <= 1'b0;
rec_pkt_done <= 1'b0; //以太网单包数据接收完成信号
case (cur_state)
st_idle :begin //等待接收前导码
if ((gmii_rx_dv == 1'b1) && (gmii_rxd == 8'h55)) begin
skip_en <= 1'b1;
end
else begin
skip_en <= 1'b0;
end
end
st_preamble :begin //接收前导码
if (gmii_rx_dv == 1'b1) begin
cnt <= cnt + 1'b1;
if ((cnt < 5'd6 ) && (gmii_rxd != 8'h55)) begin
error_en <= 1'b1;
end
else if (cnt == 5'd6) begin
cnt <= 5'd0;
if (gmii_rxd == 8'hd5) begin
skip_en <= 1'b1;
error_en <= 1'b0;
end
else begin
skip_en <= 1'b0;
error_en <= 1'b1;
end
end
else begin
skip_en <= skip_en ;
error_en <= error_en;
end
end
else begin
skip_en <= 1'b0;
error_en <= 1'b1;
end
end
st_eth_head :begin //接收以太网帧头
if (gmii_rx_dv == 1'b1) begin
cnt <= cnt + 1'b1;
if (cnt < 5'd6) begin
des_mac <= {des_mac[39:0],gmii_rxd};
end
else if( cnt == 5'd12)begin
eth_type[15:8] <= gmii_rxd;
end
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])) begin
skip_en <= 1'b1;
error_en <= 1'b0;
end
else begin
skip_en <= 1'b0;
error_en <= 1'b1;
end
end
else begin
skip_en <= skip_en ;
error_en <= error_en ;
end
end
else begin
skip_en <= 1'b0;
error_en <= 1'b1;
end
end
st_ip_head :begin //接收IP首部
if (gmii_rx_dv == 1'b1) begin
cnt <= cnt + 1'b1;
if (cnt == 5'd0) begin
ip_head_byte_num <= {gmii_rxd[3:0],2'd0};
end
else if (cnt == 5'd2) begin
total_length[15:8] <= gmii_rxd ;
end
else if (cnt == 5'd3) begin
total_length[7:0] <= gmii_rxd ;
end
else if (cnt == 5'd4) begin //有效数据字节长度,(IP首部20个字节,icmp首部8个字节,所以减去28)
icmp_data_length <= total_length - 16'd28;
end
else if (cnt == 5'd9) begin
if (gmii_rxd != ICMP_TYPE) begin //如果当前接收的数据不是ICMP协议,停止解析数据
error_en <= 1'b1 ;
skip_en <= 1'b0 ;
cnt <= 5'd0 ;
end
else begin
error_en <= 1'b0 ;
skip_en <= skip_en ;
end
end
else if ((cnt >= 5'd16)&&(cnt <= 5'd18)) begin
des_ip <= {des_ip[23:0],gmii_rxd};
end
else if (cnt == 5'd19) begin
des_ip <= {des_ip[23:0],gmii_rxd};
if ({des_ip[23:0],gmii_rxd} == BOARD_IP) begin
skip_en <= 1'b1;
error_en <= 1'b0;
cnt <= 5'd0;
end
else begin
skip_en <= 1'b0;
error_en <= 1'b1;
cnt <= 5'd0;
end
end
else begin
skip_en <= skip_en ;
error_en <= error_en ;
end
end
else begin
skip_en <= 1'b0;
error_en <= 1'b1;
end
end
st_icmp_head :begin //接收ICMP首部
if (gmii_rx_dv == 1'b1) begin
cnt <= cnt + 1 'b1;
if (cnt == 5'd0) begin
icmp_type <= gmii_rxd ;
end
else if (cnt == 5'd1) begin
icmp_code <= gmii_rxd ;
end
else if ((cnt == 5'd2)||(cnt == 5'd3)) begin
icmp_checksum <= {icmp_checksum[7:0],gmii_rxd} ;
end
else if ((cnt == 5'd4)||(cnt == 5'd5)) begin
icmp_id <= {icmp_id[7:0],gmii_rxd} ;
end
else if (cnt == 5'd6) begin
icmp_seq[15:8] <= gmii_rxd ;
end
else if (cnt == 5'd7) begin
icmp_seq[7:0] <= gmii_rxd ;
if (icmp_type == ECHO_REQUEST) begin
skip_en <= 1'b1;
error_en <= 1'b0;
cnt <= 5'd0;
end
else begin
skip_en <= 1'b0;
error_en <= 1'b1;
cnt <= 5'd0;
end
end
else begin
skip_en <= skip_en ;
error_en <= error_en ;
cnt <= cnt ;
end
end
else begin
skip_en <= 1'b0;
error_en <= 1'b1;
cnt <= 5'd0;
end
end
st_rx_data :begin //接收有效数据
if (gmii_rx_dv == 1'b1) begin
rec_en_cnt <= rec_en_cnt + 1'b1 ;
icmp_rx_cnt <= icmp_rx_cnt + 1'b1 ;
rec_data <= gmii_rxd ; //以太网接收数据
rec_en <= 1'b1 ;
if (icmp_rx_cnt == icmp_data_length - 1'b1) begin
icmp_rx_data_d0 <= 8'd0;
if (icmp_data_length[0] == 1'b1) begin //奇数
reply_checksum_add <= {8'h0,gmii_rxd} + reply_checksum_add ;
end
else begin //偶数
reply_checksum_add <= {icmp_rx_data_d0,gmii_rxd} + reply_checksum_add ;
end
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) begin
reply_checksum_add <= {icmp_rx_data_d0,gmii_rxd} + reply_checksum_add ;
end
else begin
reply_checksum_add <= reply_checksum_add ;
end
end
else begin
error_en <= 1'b1;
end
if (icmp_rx_cnt == icmp_data_length - 1'b1) begin
skip_en <= 1'b1;
error_en <= 1'b0;
rec_en_cnt <= 2'd0;
rec_pkt_done <= 1'b1;
rec_byte_num <= icmp_data_length ;
end
else begin
skip_en <= 1'b0;
error_en <= 1'b1;
rec_en_cnt <= 2'd0;
rec_pkt_done <= 1'b1;
rec_byte_num <= icmp_data_length ;
end
end
else begin
error_en <= 1'b1;
end
end
st_rx_end :begin //接收结束
rec_en <= 1'b0;
if ((gmii_rx_dv == 1'b0)&&(skip_en == 1'b0)) begin
reply_checksum <= reply_checksum_add ;
skip_en <= 1'b1 ;
reply_checksum_add <= 32'd0 ;
end
else begin
reply_checksum <= reply_checksum ;
skip_en <= skip_en ;
reply_checksum_add <= reply_checksum_add ;
end
end
default: error_en = 1'b1 ;
endcase
end
end
endmodule
(2)icmp_tx
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2024/12/28 20:19:55
// Design Name:
// Module Name: icmp_tx
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module icmp_tx(
input clk , //时钟信号
input rst_n , //复位信号,低电平有效
input [31:0] reply_checksum , //ICMP数据部分校验和
input [15:0] icmp_id , //ICMP标识符
input [15:0] icmp_seq , //ICMP序列号
input tx_start_en , //以太网开始发送信号
input [ 7:0] tx_data , //以太网待发送数据
input [15:0] tx_byte_num , //以太网发送的有效字节数
input [47:0] des_mac , //发送的目标MAC地址
input [31:0] des_ip , //发送的目标IP地址
input [31:0] crc_data , //CRC校验数据
input [7:0] crc_next , //CRC下次校验完成数据
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 define
//开发板MAC地址 00-11-22-33-44-55
parameter BOARD_MAC = 48'h00_11_22_33_44_55;
//开发板IP地址 192.168.1.10
parameter BOARD_IP = {8'd192,8'd168,8'd1,8'd10};
//目的MAC地址 ff_ff_ff_ff_ff_ff
parameter DES_MAC = 48'hff_ff_ff_ff_ff_ff;
//目的IP地址 192.168.1.102
parameter DES_IP = {8'd192,8'd168,8'd1,8'd102};
//状态机状态定义
localparam st_idle = 8'b0000_0001 ; //初始状态,等待开始发送信号
localparam st_check_sum = 8'b0000_0010 ; //IP首部校验和
localparam st_check_icmp = 8'b0000_0100 ; //ICMP首部+数据校验
localparam st_preamble = 8'b0000_1000 ; //发送前导码+帧起始界定符
localparam st_eth_head = 8'b0001_0000 ; //发送以太网帧头
localparam st_ip_head = 8'b0010_0000 ; //发送IP首部+ICMP首部
localparam st_tx_data = 8'b0100_0000 ; //发送数据
localparam st_crc = 8'b1000_0000 ; //发送CRC校验值
//以太网类型定义
localparam ETH_TYPE = 16'h0800 ; //以太网协议类型 IP协议
//以太网数据最小46个字节,IP首部20个字节+ICMP首部8个字节
//所以数据至少46-20-8=18个字节
localparam MIN_DATA_NUM = 16'd18 ;
//parameter define
//ICMP报文类型:回显应答
parameter ECHO_REPLY = 8'h00 ;
reg [7:0] cur_state ;//当前状态
reg [7:0] next_state ;//下一状态
reg start_en_d0 ;//
reg start_en_d1 ;//
reg start_en_d2 ;//
reg [15:0] tx_data_num ;//发送数据长度
reg [15:0] total_num ;//总长度
reg trig_tx_en ;//使能触发信号
reg skip_en ;//状态跳转信号
reg [4:0] cnt ;//发送数据计数器
reg [31:0] check_buffer ;//IP首部校验和
reg [31:0] check_buffer_icmp ;//ICMP首部校验和
reg [7:0] preamble [7:0] ;//前导码
reg [7:0] eth_head [13:0] ;//以太网首部
reg [31:0] ip_head [6:0] ;//IP首部 + ICMP首部
reg [1:0] tx_bit_sel ;//bit计数器
reg tx_done_t ;//发送完成标志
reg [15:0] data_cnt ;//发送数据计数器
reg [4:0] real_add_cnt ;//多发送数据计数器
wire [15:0] real_tx_data_num ; //发送有效长度
wire pos_start_en ; //触发信号上升沿
assign pos_start_en = (~start_en_d2) & start_en_d1;
assign real_tx_data_num = (tx_data_num >= MIN_DATA_NUM) ? tx_data_num : MIN_DATA_NUM;
//采tx_start_en的上升沿
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
start_en_d0 <= 1'b0;
start_en_d1 <= 1'b0;
start_en_d2 <= 1'b0;
end
else begin
start_en_d0 <= tx_start_en;
start_en_d1 <= start_en_d0;
start_en_d2 <= start_en_d1;
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 begin
if(pos_start_en && cur_state==st_idle) begin
//数据长度
tx_data_num <= tx_byte_num;
//IP长度:有效数据+IP首部长度(20bytes)+ICMP首部长度(8bytes)
total_num <= tx_byte_num + 16'd28;
end
else begin
tx_data_num <= tx_data_num ;
total_num <= total_num ;
end
end
end
//触发发送信号
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
trig_tx_en <= 1'b0;
end
else begin
trig_tx_en <= pos_start_en;
end
end
//(三段式状态机)同步时序描述状态转移
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cur_state <= st_idle ;
end
else begin
cur_state <= next_state ;
end
end
//
always @(*) begin
next_state = st_idle;
case(cur_state)
st_idle : begin //等待发送数据
if(skip_en)
next_state = st_check_sum;
else
next_state = st_idle;
end
st_check_sum: begin //IP首部校验
if(skip_en)
next_state = st_check_icmp;
else
next_state = st_check_sum;
end
st_check_icmp: begin //ICMP首部校验
if(skip_en)
next_state = st_preamble;
else
next_state = st_check_icmp;
end
st_preamble : begin //发送前导码+帧起始界定符
if(skip_en)
next_state = st_eth_head;
else
next_state = st_preamble;
end
st_eth_head : begin //发送以太网首部
if(skip_en)
next_state = st_ip_head;
else
next_state = st_eth_head;
end
st_ip_head : begin //发送IP首部+icmp首部
if(skip_en)
next_state = st_tx_data;
else
next_state = st_ip_head;
end
st_tx_data : begin //发送数据
if(skip_en)
next_state = st_crc;
else
next_state = st_tx_data;
end
st_crc: begin //发送CRC校验值
if(skip_en)
next_state = st_idle;
else
next_state = st_crc;
end
default : next_state = st_idle;
endcase
end
//
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
skip_en <= 1'b0 ;
cnt <= 5'd0 ;
check_buffer <= 32'd0 ;
check_buffer_icmp <= 32'd0 ;
ip_head[1][31:16] <= 16'd0 ;
tx_bit_sel <= 2'b0 ;
crc_en <= 1'b0 ;
gmii_tx_en <= 1'b0 ;
gmii_txd <= 8'd0 ;
tx_req <= 1'b0 ;
tx_done_t <= 1'b0 ;
data_cnt <= 16'd0 ;
real_add_cnt <= 5'd0 ;
//初始化数组
//前导码 7个8'h55 + 1个8'hd5
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地址 //目的MAC地址 ff_ff_ff_ff_ff_ff
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地址 //开发板MAC地址 00-11-22-33-44-55
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];
//以太网类型 //0800 IP协议
eth_head[12] <= ETH_TYPE[15:8];
eth_head[13] <= ETH_TYPE[7:0];
end
else begin
skip_en <= 1'b0 ;
crc_en <= 1'b0 ;
gmii_tx_en <= 1'b0 ;
tx_done_t <= 1'b0 ;
case (cur_state)
st_idle :begin
if (trig_tx_en) begin
skip_en <= 1'b1;
//版本号:4 首部长度:5(单位:32bit,20byte/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'd01,16'h0000};
//源IP地址
ip_head[3] <= BOARD_IP;
//目的IP地址
if (des_ip != 32'd0) begin
ip_head[4] <= des_ip ;
end
else begin
ip_head[4] <= DES_IP ;
end
// 8位icmp TYPE ,8位 icmp CODE
ip_head[5][31:16] <= {ECHO_REPLY,8'h00};
//16位identifier 16位sequence
ip_head[6] <= {icmp_id,icmp_seq};
//更新MAC地址
if (des_mac != 48'd0) begin
//目的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] ;
end
end
end
st_check_sum :begin //IP首部校验
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 //可能出现进位,累加一次
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
skip_en <= 1'b1 ;
cnt <= 5'd0 ;
ip_head[2][15:0] <= ~check_buffer[15:0] ;
end
else begin
check_buffer <= check_buffer;
end
end
st_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
else if(cnt == 5'd3) begin //按位取反
skip_en <= 1'b1;
cnt <= 5'd0;
// ICMP:16位校验和
ip_head[5][15:0] <= ~check_buffer_icmp[15:0];
end
else begin
check_buffer_icmp <= check_buffer_icmp;
end
end
st_preamble :begin //发送前导码+帧起始界定符
gmii_tx_en <= 1'b1 ;
gmii_txd <= preamble[cnt];
if (cnt == 5'd7) begin
skip_en <= 1'b1 ;
cnt <= 5'd0 ;
end
else begin
cnt <= cnt + 1'b1 ;
end
end
st_eth_head :begin //发送以太网首部
gmii_tx_en <= 1'b1 ;
crc_en <= 1'b1 ;
gmii_txd <= eth_head[cnt] ;
if (cnt == 5'd13) begin
skip_en <= 1'b1 ;
cnt <= 5'd0 ;
end
else begin
cnt <= cnt + 1'b1 ;
end
end
st_ip_head :begin //发送IP+ICMP首部
gmii_tx_en <= 1'b1 ;
crc_en <= 1'b1 ;
tx_bit_sel <= tx_bit_sel + 1'b1 ;
if (tx_bit_sel == 2'd0) begin
gmii_txd <= ip_head[cnt][31:24] ;
end
else if (tx_bit_sel == 2'd1) begin
gmii_txd <= ip_head[cnt][23:16] ;
end
else if (tx_bit_sel == 2'd2) begin
gmii_txd <= ip_head[cnt][15:8] ;
if(cnt == 5'd6)begin
tx_req <= 1'b1 ;
end
end
else if (tx_bit_sel == 2'd3) begin
gmii_txd <= ip_head[cnt][7:0] ;
if (cnt == 5'd6) begin //提前读请求数据,等待数据有效时发送
skip_en <= 1'b1 ;
cnt <= 5'd0 ;
end
else begin
cnt <= cnt + 1'b1 ;
end
end
else begin
gmii_txd <= gmii_txd ;
end
end
st_tx_data :begin //发送数据
gmii_tx_en <= 1'b1 ;
crc_en <= 1'b1 ;
tx_bit_sel <= 2'd0 ;
gmii_txd <= tx_data ;
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
//如果发送的有效数据少于18个字节,在后面填补充位
//补充的值为最后一次发送的有效数据
if (data_cnt + real_add_cnt < real_tx_data_num - 1'b1) begin
real_add_cnt <= real_add_cnt + 1'b1 ;
end
else begin
skip_en <= 1'b1 ;
data_cnt <= 16'd0 ;
real_add_cnt <= 5'd0 ;
end
end
else begin
data_cnt <= data_cnt ;
end
if (data_cnt == tx_data_num - 2'd2) begin
tx_req <= 1'b0 ;
end
else begin
tx_req <= tx_req ;
end
end
st_crc :begin
gmii_tx_en <= 1'b1;
tx_bit_sel <= tx_bit_sel + 3'd1;
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;
skip_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
(3)icmp
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2024/12/29 15:29:13
// Design Name:
// Module Name: icmp
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module icmp(
input sys_clk , //ila系统时钟
input rst_n , //复位信号,低电平有效
//GMII接口
input gmii_rx_clk , //GMII接收数据时钟
input gmii_rx_dv , //GMII输入数据有效信号
input [7:0] gmii_rxd , //GMII输入数据
input gmii_tx_clk , //GMII发送数据时钟
output gmii_tx_en , //GMII输出数据有效信号
output [7:0] gmii_txd , //GMII输出数据
//用户接口
output rec_pkt_done , //以太网单包数据接收完成信号
output rec_en , //以太网接收的数据使能信号
output [ 7:0] rec_data , //以太网接收的数据
output [15:0] rec_byte_num , //以太网接收的有效字节数 单位:byte
input tx_start_en , //以太网开始发送信号
input [ 7:0] tx_data , //以太网待发送数据
input [15:0] tx_byte_num , //以太网发送的有效字节数 单位:byte
input [47:0] des_mac , //发送的目标MAC地址
input [31:0] des_ip , //发送的目标IP地址
output tx_done , //以太网发送完成信号
output tx_req //读数据请求信号
);
//parameter define
//开发板MAC地址 00-11-22-33-44-55
parameter BOARD_MAC = 48'h00_11_22_33_44_55;
//开发板IP地址 192.168.1.10
parameter BOARD_IP = {8'd192,8'd168,8'd1,8'd10};
//目的MAC地址 ff_ff_ff_ff_ff_ff
parameter DES_MAC = 48'hff_ff_ff_ff_ff_ff;
//目的IP地址 192.168.1.102
parameter DES_IP = {8'd192,8'd168,8'd1,8'd102};
wire crc_en ; //CRC开始校验使能
wire crc_clr ; //CRC数据复位信号
wire [7:0] crc_d8 ; //输入待校验8位数据
wire [31:0] crc_data ; //CRC校验数据
wire [31:0] crc_next ; //CRC下次校验完成数据
wire [15:0] icmp_id ; //ICMP标识符:对于每一个发送的数据报进行标识
wire [15:0] icmp_seq ; //ICMP序列号:对于发送的每一个数据报文进行编号
//比如:发送的第一个数据报序列号为1,第二个序列号为2
wire [31:0] reply_checksum ; //接收的icmp数据部分校验和
assign crc_d8 = gmii_txd ;
icmp_rx
#(
.BOARD_MAC (BOARD_MAC ) , //参数例化
.BOARD_IP (BOARD_IP )
)
u_icmp_rx(
.clk (gmii_rx_clk ) ,
.rst_n (rst_n ) ,
.gmii_rx_dv (gmii_rx_dv ) , //GMII输入数据有效信号
.gmii_rxd (gmii_rxd ) , //GMII输入数据
.rec_pkt_done (rec_pkt_done ) , //以太网单包数据接收完成信号
.rec_en (rec_en ) , //以太网接收数据使能信号 ---fifo_en
.rec_data (rec_data ) , //以太网接收数据 ---fifo_data
.rec_byte_num (rec_byte_num ) , //以太网接收有效字数 bit
.icmp_id (icmp_id ) , //ICMP标识符
.icmp_seq (icmp_seq ) , //ICMP序列号
.reply_checksum (reply_checksum ) //接收数据校验
);
icmp_tx
#(
.BOARD_MAC (BOARD_MAC ) , //参数例化
.BOARD_IP (BOARD_IP ) ,
.DES_MAC (DES_MAC ) ,
.DES_IP (DES_IP )
)
u_icmp_tx(
//input
.clk (gmii_tx_clk ) , //时钟信号
.rst_n (rst_n ) , //复位信号,低电平有效
.reply_checksum (reply_checksum ) , //ICMP数据部分校验和
.icmp_id (icmp_id ) , //ICMP标识符
.icmp_seq (icmp_seq ) , //ICMP序列号
.tx_start_en (tx_start_en ) , //以太网开始发送信号
.tx_data (tx_data ) , //以太网待发送数据
.tx_byte_num (tx_byte_num ) , //以太网发送的有效字节数
.des_mac (des_mac ) , //发送的目标MAC地址
.des_ip (des_ip ) , //发送的目标IP地址
.crc_data (crc_data ) , //CRC校验数据
.crc_next (crc_next ) , //CRC下次校验完成数据
//output
.tx_done (tx_done ) , //以太网发送完成信号
.tx_req (tx_req ) , //读数据请求信号
.gmii_tx_en (gmii_tx_en ) , //GMII输出数据有效信号
.gmii_txd (gmii_txd ) , //GMII输出数据
.crc_en (crc_en ) , //CRC开始校验使能
.crc_clr (crc_clr ) //CRC数据复位信号
);
crc32_d8 u_crc32_d8(
.clk (gmii_tx_clk ) , //时钟信号
.rst_n (rst_n ) , //复位信号,低电平有效
.data (crc_d8 ) , //输入待校验8位数据
.crc_en (crc_en ) , //crc使能,开始校验标志
.crc_clr (crc_clr ) , //crc数据复位信号
.crc_data (crc_data ) , //CRC校验数据
.crc_next (crc_next ) //CRC下次校验完成数据
);
endmodule