下面介绍各层协议的具体功能及其帧格式。
2.3.1 MAC协议以太网MAC层在网络通信中起着至关重要的作用,它负责数据的封装与拆包、传输控制、错误检测与纠正功能,确保数据在网络中正确传输和有效处理。
MAC数据帧格式如下图2.1所示,其包头包含前导码、SFD、目的MAC地址、源MAC地址、类型/长度,最后4个字节为CRC校验位。
各区域具体功能如下:
前导码(7Byte):用于调整时钟使收发节点的时钟同步,接收端通过识别前导码来确定数据帧的边界,以确保接收端能够准确地提取出完整的数据帧。内容为连续7字节的8’h55。
帧起始定界符SFD(1Byte):用于区分前导段和数据段,确保后续数据的正确解析。内容为8’hd5。
MAC地址(6Byte):MAC地址由48bit数据组成,它是网卡的物理地址,一般固化在网卡的ROM中。在以太网传输的最底层就是根据MAC地址来发送数据,同一个网络里不能有两个相同的MAC地址。MAC地址的前3个字节是组织唯一标识符,用于标识网络硬件制造商,后3个字节是制造商为网卡分配的序列号。
类型/长度(2Byte):该区域可以用来表示MAC数据包下一层的类型,也可以用来描述MAC数据包数据段的长度。该值小于1536表示长度,大于1536表示类型,IP协议对应的数值为0x0800,ARP协议为0x0806。
FCS(4Byte):FCS(错误检测码)是用于检测数据帧在传输过程中是否发生错误的一个字段,它通过CRC(循环冗余校验)算法计算得出,并附加在数据帧的尾部。发送数据时,会根据数据内容生成简短的校验和,并将其与数据一起发送。接收数据时,将再次生成的校验和并将其与发送的校验和进行比较。如果二者相等,则数据没有损坏。
每一帧传输完成后,都必须等待96bit数据传输的时间,即最小帧间隔(IFG),才可以进行下一次以太网帧的传输。由于以太网每个时钟周期发送8bit数据,所以最小帧间隔为12个时钟周期。
2.3.2 CRC校验CRC校验的实现原理比较简单。设m(x)为需要校验的数据,g(x)为多项式,先将数据左移K位(K为g(x)的最大项系数),然后做模2除法运算,模二运算的本质是异或运算。
比如,当m(x)为101011,g(x)为X3+X+1时,将101011左移3位得到101011000,与1011进行模2除法,过程和结果如下图所示。计算得到的余数为111,将其附加到m(x)数据后得到101011010,即为新的数据帧,该数据与1011进行模二除法得到的余数为0,说明校验正确。
硬件设计中,可以通过串行或并行的方式来实现CRC的计算。串行方式消耗的资源少,但速度较慢。串行处理的硬件电路实现实际上就是寄存器和异或门电路连接成线性反馈移位寄存器。CRC-32生成的多项式为g(x)=x32+x26+x23+x22+x16+x12+x11+x10+x8+x7+x5+x4+x2+x+1,其硬件实现如下图所示。
用c1[31:0]表示移位寄存器的次态,用c[31:0]表示移位寄存器的现态,用d[0:0]表示需要校验的串行数据,得到的布尔表达式如下:
- c1[0 ] = d[0] ^ c[31];
- c1[1 ] = d[0] ^ c[0] ^ c[31];
- c1[2 ] = d[0] ^ c[1] ^ c[31];
- c1[3 ] = c[2];
- c1[4 ] = d[0] ^ c[3] ^ c[31];
- c1[5 ] = d[0] ^ c[4] ^ c[31];
- c1[6 ] = c[5];
- c1[7 ] = d[0] ^ c[6] ^ c[31];
- c1[8 ] = d[0] ^ c[7] ^ c[31];
- c1[9 ] = c[8];
- c1[10] = d[0] ^ c[9] ^ c[31];
- c1[11] = d[0] ^ c[10] ^ c[31];
- c1[12] = d[0] ^ c[11] ^ c[31];
- c1[13] = c[12];
- c1[14] = c[13];
- c1[15] = c[14];
- c1[16] = d[0] ^ c[15] ^ c[31];
- c1[17] = c[16];
- c1[18] = c[17];
- c1[19] = c[18];
- c1[20] = c[19];
- c1[21] = c[20];
- c1[22] = d[0] ^ c[21] ^ c[31];
- c1[23] = d[0] ^ c[22] ^ c[31];
- c1[24] = c[23];
- c1[25] = c[24];
- c1[26] = d[0] ^ c[25] ^ c[31];
- c1[27] = c[26];
- c1[28] = c[27];
- c1[29] = c[28];
- c1[30] = c[29];
- c1[31] = c[30];
复制代码
并行方式消耗的资源多,但速度快,适合用于使用千兆以太网或万兆以太网的场景。得到8位并行运算的结果,需要对d[7:0]的每一位做迭代运算,对d[0]计算得到的结果已经列出,对d[1]做同样的运算得到的布尔表达式如下:
- c2[0 ] = d[0] ^ c[30];
- c2[1 ] = d[1] ^ d[0] ^ c[30] ^ c[31];
- c2[2 ] = d[1] ^ d[0] ^ c[0] ^ c[30] ^ c[31];
- c2[3 ] = d[1] ^ c[1] ^ c[31];
- c2[4 ] = d[0] ^ c[2] ^ c[30];
- ... ...
- c2[31] = c[29];
复制代码
经过8次迭代运算即可得到CRC-32的8位并行运算公式。
2.3.3 PAUSE流量控制帧格式PAUSE帧是一种用于控制数据流量的控制帧,当对端发来的数据量过大,为了防止缓冲区溢出导致的数据丢失,MAC层控制流量控制子层发出流量控制帧。接收方收到PAUSE帧后,会将其内容送到MAC层中的流量控制模块中进行解析,得到需要暂停发送的时间。在一帧结束后开启暂停,暂停计数结束后,开始发送新一帧的数据。
PAUSE帧的格式如下图所示。
目的MAC(6Byte):PAUSE帧的目的为保留的组播地址,其值为固定的48’h01_80_c2_00_00_01。
TYPE(2Byte):固定值为16’h8808,表示帧类型为MAC控制帧。
OPCODE(2Byte):操作码,值为16’h0001,表示MAC控制帧中的PAUSE帧。
TIME(2Byte):时间参数,它的值表示以太网以当前速率传输512bit数据的时间,接收方实际暂停的时间为该字段数值左移6位得到的值乘时钟周期。
补充数据:有效信息后补0,补齐MAC帧所需要的最小数据数量。
2.3.4 IP协议
MAC数据帧经过数据链路层传输到网络层时,前导码、SFD、MAC地址、类型/长度以及校验字节均被过滤,IP数据包传入了网络层。该数据包也不完全是有效数据,其还包含20字节的IP头部,具体见上图。IP头部各区域功能如下:
版本(4bit):定义IP协议版本,IPV4为4’h4。
首部长度(4bit):定义IP数据包头部长度,表示具有32位字长数据的数量。最小值为5,最大值为15。
服务类型(8bit):用于分配优先级、延迟、吞吐量及可靠性,一般为8’h00。
总长度(16bit):定义整个IP数据包长度。
标识(8bit):发完一包数据自动加1。
标记(3bit):最高位保留为0;中间位是否开启分段,1不开启,0开启;最低位表示是否存在下一个分段,0表示为最后一个分段,1表示还存在下一个分段。一般默认为3’b010。
分段偏移(13bit):表示分段数据在源数据报中的相对位置。
生存时间(8bit):表示以太网数据包可以中转进过多少个路由器,每进过一个路由器,该值就会减少1,直到该值变成0,丢包该包。WIN系统默认为8’h80。
协议(8bit):指出IP处理过程完成后,传输层的协议。UDP为8’d17,TCP为8’d6,ICMP为8’d1。
首部校验和(16bit):该区域确保IP协议头部的完整性。将头部每16位划分为一部分,将各部分相加取得的结果再取反码,即为该区域数据。
源地址(32bit):源主机IP地址。
目的地址(32bit):目的主机IP地址。
2.3.5 IP首部校验IP首部校验的原理是将IP数据包头部每16bit的数据划分、校验位置0,进行累加操作,得到的结果若有进位,则将进位数据和低16bit数据进行相加,直到不再产生进位,把得到的结果取反,就是IP首部校验和的值。
发送方对首部校验和计算,并将其结果放入IP头部的首部校验和字段。接收方接收到IP数据包时,将其所有的包头数据进行校验,若得到的校验结果为全1,则数据没有发生改变。
2.3.6 UDP协议UDP数据报文分为UDP头部和有效数据。UDP头部由源端口号、目的端口号、长度以及校验和组成,如下图所示。相比TCP协议,UDP的传输效率更高,但其提供不可靠传输,有可能丢包。各区域的功能如下:
UDP数据包分为UDP头部和有效数据。UDP头部由源端口号、目的端口号、长度以及校验和组成,如下图所示。相比TCP协议,UDP的传输效率更高,但其提供不可靠传输,有可能丢包。各区域的功能如下:
源端口号(16bit):源主机的应用程序使用的端口号。
目的端口号(16bit):目的主机的应用程序使用的端口号。
UDP长度(16bit):UDP头部和UDP数据的字节长度之和。
UDP校验和(16bit):检查数据是否被正确接收,可以不使用。
2.3.7 ARP协议ARP即地址解析协议的基本功能是通过目标设备的IP地址,查询目标设备的MAC地址。在发送IP数据包时,发送方首先查看自己的ARP链表,确定IP地址对应的MAC地址。如果找到了对应的MAC地址,则利用ARP链表中的MAC地址对IP数据包进行封装,将数据包发送出去;如果没有找到对应的MAC地址,则发送ARP广播,请求接收方将自己的MAC地址通过ARP应答发送到请求方,然后发送方将ARP应答包中的地址信息缓存进ARP链表中。
ARP数据包的结构如下图:
硬件类型(2Byte):硬件地址的类型,以太网的硬件类型为1。
协议类型(2Byte):上层协议类型,IP的协议类型为16’h0800。
硬件地址长度(1Byte):硬件地址(MAC地址)的长度,以字节为单位,其值固定为8’h06。
协议地址长度(1Byte):IP地址的长度,以字节为单位,固定值8’h04。
OPCODE(2Byte):操作码,值为16’h0001表示ARP请求,值为16’h0002表示ARP应答。
源MAC地址(6Byte):发送方的MAC地址。
源IP地址(4Byte):发送方的IP地址。
目的MAC地址(6Byte):发送ARP请求时为广播地址48’hff_ff_ff_ff_ff_ff,发送ARP应答时为接收方的MAC地址。
目的IP地址(4Byte):接收端的IP地址。
2.3.8 ICMP协议ICMP协议主要用于诊断网络连接故障和测试网络连接,PC端通常会使用ping命令来测试网络,ping命令就是基于ICMP协议实现的。
由于UDP协议提供不可靠传输,所以需要和ICMP协议配合使用以达到查询和纠错目的。ICMP报文分为查询报文和差错报文两大类,本协议栈实现了对远程主机发送的ping请求回复一个ping应答包。ping请求和ping应答都属于查询报文,查询报文的报文格式如下:
类型(1Byte):表示ICMP报文的类型,不同类型代表不同的功能,如:8’h08表示回显请求,8’h00表示回显应答。
代码(1Byte):和类型字段结合使用,共同标识了ICMP报文的详细类型,回显请求和回显应答是该值为8’h00。
校验和(2Byte):用于判断数据传输时是否出了差错,与IP首部校验的方式相同。
标识符(2Byte):对数据报进行标识,用于区分不同的请求和应答。序列号(2Byte):每次发送完就加1,和标识符一起用于区分不同的请求和应答。
3 程序设计前面我们介绍了以太网的基本概念,及涉及的各层协议格式,接下来我们通过设计Verilog程序来实现以太网各个子层的功能。程序整体架构图如下:
3.1 MAC层MAC层一边连接GMII接口,一边连接上层协议传来的数据。该层将上层传来的数据包组帧发送出去,或者将接收到的数据帧解析,将信息和拆解出来的数据包传给上层协议。
3.1.1 MAC接收模块MAC接收模块主要实现以下几个功能:
(1)对接收到的MAC帧进行解析,过滤前导码、帧起始定界符、MAC地址、类型、CRC校验位,将上层数据包提取出来,并缓存数据包类型,传给上层做判断。
(2)对每帧数据进行CRC校验,与帧末尾的4字节校验位做比较,判断数据的正确性
(3)识别接收到的流控帧,将有效信息发送给子层解析,把解析到的暂停时间和源MAC地址输出至MAC接收模块做进一步处理。
(4)通过FIFO完成PHY接收时钟和用户接口时钟之间的时钟域的转换,并将数据包输出至上层。
- /*******************************uimac_rx模块*********************
- --以下是米联客设计的uimac_rx模块
- --本模块主要有以下几个功能
- --1. 从外部PHY芯片接收mac帧,解析mac帧首部,进行mac地址过滤和帧类型过滤。
- --2. 内部子模块crc32_check对每帧数据进行crc32值的计算,判断数据的正确性。
- --3. 识别接收的mac流控帧,子模块mac_frame_ctrl提取流控帧中的暂停时间和源mac地址输出至uimac_tx模块。
- --4. mac_rx_data_fifo完成phy接收时钟和用户接口时钟之间的时钟域转换,将数据输出。
- *********************************************************************/
- `timescale 1ns/1ps
- module uimac_rx
- (
- input wire [47:0] I_mac_local_addr , //本地MAC地址
- input wire I_crc32_en , //使能CRC校验
- input wire I_reset , //系统复位
- //MAC接收数据发送给上层协议
- input wire I_mac_rclk , //接收时钟
- output wire O_mac_rvalid , //MAC帧数据有效
- output wire [7:0] O_mac_rdata , //MAC有效数据
- output wire [15:0] O_mac_rdata_type , //MAC类型
- output wire O_mac_rdata_error ,
- //发送PAUSE控制到mac_send
- output wire O_mac_pause_en ,
- output wire [21:0] O_mac_pause_time ,
- output wire [47:0] O_mac_pause_addr ,
- //从硬件层获取的裸MAC数据
- input wire I_gmii_rclk , //rgmii接收时钟
- input wire I_gmii_rvalid , //gmii接收数据有效使能信号
- input wire [7:0] I_gmii_rdata //gmii接收数据
- );
-
- wire [7:0] mac_rdata;
- reg mac_rdata_valid;
- reg [15:0] mac_rdata_type;
- reg mac_rdata_error;
-
- assign O_mac_rdata = mac_rdata;
- assign O_mac_rvalid = mac_rdata_valid;
- assign O_mac_rdata_type = mac_rdata_type;
- assign O_mac_rdata_error = mac_rdata_error;
-
- reg [10:0] mac_rdata_cnt;
- reg mac_wfifo_en; //FIFO写入数据有效使能
- reg mac_rfifo_en; //FIFO读数据使能
-
- reg crc_en; //crc校验使能
- reg [2:0] crc_cnt; //移位计数器
- wire [31:0] crc_data_out; //crc校验结果输出
- reg [2:0] STATE; //写FIFO状态机
- reg [1:0] S_RFIFO; //读FIFO状态机
-
- reg [47:0] dst_mac_addr; //MAC帧解析出的目的MAC地址(接收方的MAC地址)
- reg [47:0] src_mac_addr; //MAC帧解析出的源MAC地址(发送方的MAC地址)
- reg [15:0] mac_frame_type; //上层数据包类型(0x0800 ip;0x0806 arp;0x8808 mac_ctrl)
- reg mac_pause_en; //PAUSE帧有效使能
- reg [3:0] cnt; //对MAC帧头部的字节数计数
-
- reg [10:0] mac_wfifo_data_cnt_info;//写帧信息字节数到信息FIFO
- reg mac_wfifo_en_info; //写帧信息FIFO有效使能
- reg mac_rfifo_en_info; //读帧信息FIFO有效使能
- wire [26:0] mac_rfifo_data_info; //从信息FIFO读出帧信息
- reg [10:0] mac_rdata_len; //mac帧长度
- wire mac_rfifo_empty_info; //信息FIFO读空信号
-
- reg [7:0] mac_rdata_r1, mac_rdata_r2, mac_rdata_r3, mac_rdata_r4;//打拍
- reg mac_rvalid_r1, mac_rvalid_r2, mac_rvalid_r3, mac_rvalid_r4;//打拍
-
- localparam WAIT_SFD = 3'd0;
- localparam CHECK_MAC_HEADER = 3'd1;
- localparam WRITE_FIFO = 3'd2;
- localparam RECORD_FRAME_LENGTH = 3'd3;
- localparam WAIT_FRAME_END = 3'd4;//STATE
-
- localparam WAIT_MAC_FRAME = 2'd0;
- localparam READ_MAC_FRAME_DATA_LENGTH = 2'd1;
- localparam READ_MAC_FRAME_DATA = 2'd2;//S_RFIFO
-
- localparam ARP_TYPE = 16'h0806;
- localparam IP_TYPE = 16'h0800;
- localparam MAC_CONTROL_TYPE = 16'h8808;
复制代码
由于用户接口时钟和PHY芯片接收时钟不同源,因此需要对输入的数据进行跨时钟域处理,再将数据传至上层。由于需要传输的数据量比较大,且用FIFO做跨时钟域比较简单,所以程序中使用异步FIFO做跨时钟域处理。 在uimac_rx和uimac_tx发送模块中,都使用了两个FIFO做跨时钟域处理,一个FIFO用来缓存数据,另一个FIFO用来缓存需要传递的信息,在一帧输入接收完成后,将信息写入帧信息FIFO,通过FIFO空标志信号来控制一帧数据读出。
信号打4拍再写入数据FIFO是为了过滤掉4字节CRC校验位。
- assign O_mac_pause_addr = src_mac_addr;
-
- always@(posedge I_gmii_rclk) begin
- mac_rdata_r1 <= I_gmii_rdata;
- mac_rdata_r2 <= mac_rdata_r1;
- mac_rdata_r3 <= mac_rdata_r2;
- mac_rdata_r4 <= mac_rdata_r3;
- end
-
- always@(posedge I_gmii_rclk) begin
- mac_rvalid_r1 <= I_gmii_rvalid;
- mac_rvalid_r2 <= mac_rvalid_r1;
- mac_rvalid_r3 <= mac_rvalid_r2;
- mac_rvalid_r4 <= mac_rvalid_r3;
- end//打4拍,方便fifo只写入有效数据,而不写入crc校验位
-
- mac_rx_data_fifo mac_rx_data_fifo (
- .rst (I_reset),
- .wr_clk (I_gmii_rclk),
- .din (mac_rdata_r4),
- .wr_en (mac_wfifo_en & I_gmii_rvalid),//mac_wfifo_en控制只写入有效数据部分,I_gmii_rvalid控制最后的CRC部分不写入
-
- .rd_clk (I_mac_rclk),
- .rd_en (mac_rfifo_en),
- .dout (mac_rdata),
- .full (),
- .empty (),
- .rd_data_count (),
- .wr_data_count ()
- );
-
- mac_rx_frame_fifo mac_rx_frame_fifo (
- .rst (I_reset),
- .wr_clk (I_gmii_rclk),
- .din ({mac_wfifo_data_cnt_info,mac_frame_type}),
- .wr_en (mac_wfifo_en_info),
-
- .rd_clk (I_mac_rclk),
- .rd_en (mac_rfifo_en_info),
- .dout (mac_rfifo_data_info),
- .full (),
- .empty (mac_rfifo_empty_info)
- );
-
- crc32_check crc32_check
- (
- .reset (I_reset),
- .clk (I_gmii_rclk),
- .CRC32_en (crc_en & I_crc32_en),
- .CRC32_init (~mac_rvalid_r4),
- .data (mac_rdata_r4),
- .CRC_data (crc_data_out)
- );
-
- //mac帧控制,当接收方来不及处理接收数据,需要进行帧控制,通知发送模块。
- uimac_tx_frame_ctrl mac_tx_frame_ctrl
- (
- .I_clk (I_gmii_rclk),
- .I_reset (I_reset),
- .I_mac_pause_en (mac_rvalid_r4 & mac_pause_en),
- .I_mac_data (mac_rdata_r4),
- .O_mac_pause_en (O_mac_pause_en),
- .O_mac_pause_time (O_mac_pause_time)
- );
复制代码
通过状态机分别对FIFO写入数据和FIFO读出数据的时序进行控制。FIFO写入数据的状态机转换图如图所示。
WAIT_SFD:初始状态为WAIT_SFD,等待接收到帧起始定界符8’hd5时,跳转到CHECK_MAC_HEADER帧头接收状态。
CHECK_MAC_HEADER:通过计数器将每一字节的帧头信息缓存,帧头信息接收完成后,判断接收到数据包的类型。如果接收到的MAC帧类型为IP包或者ARP包,跳转至WRITE_FIFO状态,并将数据FIFO写使能拉高,将有效数据写入数据FIFO,如果接收到的MAC帧类型为流量控制帧,则将有效数据传入帧流控制模块,对信息进行解析,跳转至FRAME_END状态,状态如果以上情况都不是,丢弃该帧,跳转至FRAME_END状态。
WRITE_FIFO:当数据有效信号为高时,将有效数据写入写数据FIFO,使用I_gmii_rvalid做为计数器计数的有效信号,是为了统计到有效数据的准确长度。CRC校验完成后,将帧长度和帧类型写入帧信息FIFO中,进入RECORD_FRAME_LENGTH状态。
RECORD_FRAME_LENGTH:帧信息写入帧信息FIFO完成后,回到WAIT_SFD状态,等待接收下一帧。
WAIT_FRAME_END:等待不传入上层的帧结束,回到WAIT_SFD状态,等待接收下一帧。
复制代码
FIFO读出数据的状态机转换图如图所示。
WAIT_MAC_FRAME:当帧信息FIFO的空信号拉低,表明一帧数据已经接收完毕,将帧信息FIFO缓存的帧信息读出,并跳转至READ_MAC_FRAME_DATA_LENGTH状态。
READ_MAC_FRAME_DATA_LENGTH:该状态下开始读出数据FIFO缓存的有效数据,并将数据有效信号拉高,开始向上层传输数据包,随即进入READ_MAC_FRAME_DATA状态。
READN_MAC_FRAME_DATA:对读出的数据数量计数,当读出的数据量为帧信息FIFO中读出的帧长度时,停止读出数据,返回WAIT_MAC_FRAME状态。
- always@(posedge I_mac_rclk or posedge I_reset) begin
- if(I_reset) begin
- mac_rfifo_en_info <= 1'b0;
- mac_rdata_len <= 11'd0;
- mac_rdata_cnt <= 11'd0;
- mac_rfifo_en <= 1'b0;
- mac_rdata_type <= 16'd0;
- mac_rdata_valid <= 1'b0;
- S_RFIFO <= WAIT_MAC_FRAME;
- end
- else begin
- case(S_RFIFO)
- WAIT_MAC_FRAME:begin
- if(!mac_rfifo_empty_info) begin//接收mac帧信息fifo非空
- mac_rfifo_en_info <= 1'b1;
- S_RFIFO <= READ_MAC_FRAME_DATA_LENGTH;
- end
- else
- S_RFIFO <= WAIT_MAC_FRAME;
- end
- READ_MAC_FRAME_DATA_LENGTH:begin
- mac_rdata_len <= mac_rfifo_data_info[26:16];//mac帧长度
- mac_rdata_type <= mac_rfifo_data_info[15:0];//mac类型
- mac_rfifo_en_info <= 1'b0;
- mac_rfifo_en <= 1'b1;//读数据fifo
- mac_rdata_valid <= 1'b1;//数据有效
- S_RFIFO <= READ_MAC_FRAME_DATA;
- end
- READ_MAC_FRAME_DATA:begin
- if(mac_rdata_cnt < (mac_rdata_len - 1'b1)) begin//读完一帧数据
- mac_rdata_cnt <= mac_rdata_cnt + 1'b1;
- S_RFIFO <= READ_MAC_FRAME_DATA;
- end
- else begin
- mac_rfifo_en <= 1'b0;
- mac_rdata_valid <= 1'b0;
- mac_rdata_cnt <= 11'd0;
- mac_rdata_len <= 11'd0;
- mac_rdata_type <= 16'd0;
- S_RFIFO <= WAIT_MAC_FRAME;
- end
- end
- endcase
- end
- end
复制代码
3.1.2 MAC发送模块MAC发送模块主要实现以下功能:
(1)接收IP或ARP数据包,添加MAC帧首部,并对长度不足64字节的包进行补0。
(2)通过CRC校验模块,生成CRC校验值添加在帧的末尾。
(3)通过流控模块,接收MAC接收模块发送的暂停信号,进行流量控制。
(4)通过FIFO完成PHY发送时钟和用户接口时钟之间的时钟域的转换,并将数据输出至外部PHY芯片。
该模块数据跨时钟域转换的方式和uimac_rx模块类似,使用两个FIFO对数据进行处理。通过DR1_LOGIC_SHIFTER原语将数据延时,将帧头插入,该方法在uiip_tx和uiudp_tx模块中也有所体现,该原语可在TD安装路径下的arch文件夹的dr1_macro.v文件中找到。
控制FIFO写入数据的状态机跳转图如所示。
WAIT_DATA_PACKET:当数据发送过来后,开始接收数据并缓存进FIFO中,并通过寄存器缓存帧信息,进入WIRTE_FIFO状态。如果数据FIFO写计数器大于阈值(本工程设置为2500,具体数值根据FIFO深度而定),则停止接收该帧,保持原状态不变。
WRITE_FIFO:写数据到数据FIFO中,并且对写入的数据长度进行计数,如果数据长度小于46,对数据末尾进行补0,一帧数据写入完成后,将长度、类型和地址信息写入帧信息FIFO,进入RECORD_DATA_PACKET_INFO状态。
RECORD_DATA_PACKET_INFO:信息写入帧信息FIFO完成后,回到WAIT_DATA_PACKET状态,等待接收下一帧数据。
- always@(posedge I_mac_tclk or posedge rst) begin
- if(rst) begin
- mac_wfifo_en_info <= 1'b0; //MAC消息FIFO,把MAC的信息包括,目的MAC地址、有效数据长度、帧类型写入到info fifo暂存
- mac_wfifo_data_addr_info <= 48'd0; //MAC目的地址,暂存info fifo
- mac_wfifo_data_type_info <= 16'd0; //MAC帧类型,暂存info fifo
- mac_wfifo_data_cnt_info <= 11'd0; //MAC数据部分发送字节计数器
- mac_wfifo_en <= 1'b0; //将帧数据写入到mac_tx_data_fifo缓存
- mac_wfifo_data <= 8'd0; //将帧数据写入到mac_tx_data_fifo缓存
- O_mac_tbusy <= 1'b1; //通知外部模块,非忙
- S_WFIFO <= WAIT_DATA_PACKET;
- end
- else begin
- case(S_WFIFO)
- WAIT_DATA_PACKET:begin
- if(mac_wfifo_data_cnt > SEND_PAUSE_THRESHOLD) begin//当FIFO写通道数据计数器大于SEND_PAUSE_THRESHOLD,不进行新的一帧传输,O_mac_tbusy为握手信号,不进行握手(拉高)
- O_mac_tbusy <= 1'b0;
- S_WFIFO <= WAIT_DATA_PACKET;
- end
- else begin
- if(I_mac_tvalid) begin//当有效数据发送过来后开始接收数据并且缓存到FIFO
- O_mac_tbusy <= 1'b1; //uimac_tx 忙
- mac_wfifo_en <= 1'b1; //将数据写入FIFO
- mac_wfifo_data <= I_mac_tdata; //写入FIFO的数据
- mac_wfifo_data_addr_info<= I_mac_tdest_addr; //目的MAC地址
- mac_wfifo_data_type_info<= {14'd0, I_mac_tdata_type};//数据类型
- mac_wfifo_data_cnt_info <= mac_wfifo_data_cnt_info + 1'b1;//一帧数据的长度,以BYTE为单位
- S_WFIFO <= WRITE_FIFO;//进入下一个状态等待写FIFO
- end
- else begin
- O_mac_tbusy <= 1'b0; //uimac_tx 非忙
- S_WFIFO <= WAIT_DATA_PACKET;
- end
- end
- end
- WRITE_FIFO:begin//写数据到FIFO该FIFO用于缓存udp协议发送过来的数据
- if(I_mac_tvalid) begin//一帧数据接收过程中O_gmii_tdata_valid始终为高电平
- mac_wfifo_en <= 1'b1;//继续写FIFO
- mac_wfifo_data <= I_mac_tdata;//写入FIFO的数据
- mac_wfifo_data_cnt_info <= mac_wfifo_data_cnt_info + 1'b1;//帧字节计数器累加
- S_WFIFO <= WRITE_FIFO;
- end
- else begin
- if(mac_wfifo_data_cnt_info < 11'd46) begin//当一包/帧数据的长度小于46字节,自动补0(一帧数据最小64bytes,其中数据部分最小46bytes)
- mac_wfifo_en <= 1'b1;
- mac_wfifo_data <= 8'd0;
- mac_wfifo_en_info <= 1'b0;
- mac_wfifo_data_cnt_info <= mac_wfifo_data_cnt_info + 1'b1;
- S_WFIFO <= WRITE_FIFO;
- end
- else begin//当一包/帧数据接收完,写包/帧信息 到包/帧信息FIFO
- mac_wfifo_en <= 1'b0;
- mac_wfifo_data <= 8'd0;
- mac_wfifo_en_info <= 1'b1;
- S_WFIFO <= RECORD_DATA_PACKET_INFO;
- end
- end
- end
- RECORD_DATA_PACKET_INFO:begin//时序中,该周期完成写包/帧信息 到包/帧信息FIFO
- mac_wfifo_en_info <= 1'b0;
- mac_wfifo_data_addr_info <= 48'd0;
- mac_wfifo_data_type_info <= 16'd0;
- mac_wfifo_data_cnt_info <= 11'd0;
- S_WFIFO <= WAIT_DATA_PACKET;
- end
- endcase
- end
- end
复制代码
控制FIFO读出数据的状态机转换图如图所示。
WAIT_DATA_PACKET:帧信息FIFO非空,说明有帧需要发送,将帧信息FIFO读使能拉高一个时钟周期,跳转至READ_DATA_PACKET_INFO状态。
READ_DATA_PACKET_INFO:对读出的帧信息进行解析,通过帧类型得到目地MAC地址,并拉高数据FIFO读使能,开始读出有效数据,输入进移位寄存器中,进入RAED_DATA_PACKET状态。如果PAUSE标志信号为高,且PAUSE帧的地址和目的MAC地址相同,说明对方请求暂停发送,则进入WAIT_PAUSE_END状态。
READ_DATA_PACKET:当一帧数据读完后,进入WAIT_CRC_TRANS_DONE状态。
WAIT_CRC_TRANS_DONE:等待CRC计数器置零,代表一帧数据发送完成,进入ADD_IFG状态。
ADD_IFG:等待最小帧间隔结束,回到WAIT_DATA_PACKET状态,等待接收下一帧。
WAIT_PAUSE_END:等待PAUSE标志信号拉低,暂停结束,此时将数据FIFO读使能拉高,继续读该帧的有效数据,进入READ_DATA_PACKET状态。
READ_DATA_PACKET:将shift_ram移位后的数据进行组帧,通过计数器添加前导码、SFD、目的MAC、源MAC和类型字段,并在帧末尾填充4字节CRC校验数据。
复制代码
3.1.3 CRC校验模块通过Easics CRC Tool网页生成CRC校验代码,将代码稍作修改,得到CRC校验计算模块。uimac_tx将得到的校验和添加到帧末尾,uimac_rx模块将接收到的CRC校验位也进行CRC校验,若校验计算结果为32'hc704dd7b则校验正确,该值由CRC32多项式32’h1cdf4421经过位反演和字节反转得到。在发送和接收CRC校验和时,其值是以低字节序进行传输的,即最高位在末尾,发送时需要注意将CRC校验结果转为低字节序。
- `timescale 1ns / 1ps
- module crc32_gen(
- input reset,
- input clk,
- input CRC32_en, //CRC校验使能信号
- input CRC32_init, //CRC校验值初始化信号
- input CRC_read,
- //input CRC32_valid, //CRC校验值维持有效
- input [7:0] data,
- output [7:0] CRC_out
- );
- reg [31:0] CRC_temp;
- assign CRC_out = CRC_read ? ~{CRC_temp[24], CRC_temp[25], CRC_temp[26], CRC_temp[27],
- CRC_temp[28], CRC_temp[29], CRC_temp[30], CRC_temp[31]} : 8'h00;
-
- always@(posedge clk or posedge reset)
- if(reset)
- CRC_temp <= 32'hffffffff;
- else if(CRC32_init)
- CRC_temp <= 32'hffffffff;
- else if(CRC32_en)
- begin
- CRC_temp[0]<=CRC_temp[24]^CRC_temp[30]^data[1]^data[7];
- CRC_temp[1]<=CRC_temp[25]^CRC_temp[31]^data[0]^data[6]^CRC_temp[24]^CRC_temp[30]^data[1]^data[7];
- CRC_temp[2]<=CRC_temp[26]^data[5]^CRC_temp[25]^CRC_temp[31]^data[0]^data[6]^CRC_temp[24]^CRC_temp[30]^data[1]
- ^data[7];
- CRC_temp[3]<=CRC_temp[27]^data[4]^CRC_temp[26]^data[5]^CRC_temp[25]^CRC_temp[31]^data[0]^data[6];
- CRC_temp[4]<=CRC_temp[28]^data[3]^CRC_temp[27]^data[4]^CRC_temp[26]^data[5]^CRC_temp[24]^CRC_temp[30]^data[1]
- ^data[7];
- CRC_temp[5]<=CRC_temp[29]^data[2]^CRC_temp[28]^data[3]^CRC_temp[27]^data[4]^CRC_temp[25]^CRC_temp[31]^data[0]^data[6]^CRC_temp[24]^CRC_temp[30]^data[1]^data[7];
- CRC_temp[6]<=CRC_temp[30]^data[1]^CRC_temp[29]^data[2]^CRC_temp[28]^data[3]^CRC_temp[26]^data[5]^CRC_temp[25]^CRC_temp[31]^data[0]^data[6];
- CRC_temp[7]<=CRC_temp[31]^data[0]^CRC_temp[29]^data[2]^CRC_temp[27]^data[4]^CRC_temp[26]^data[5]^CRC_temp[24]^data[7];
- CRC_temp[8]<=CRC_temp[0]^CRC_temp[28]^data[3]^CRC_temp[27]^data[4]^CRC_temp[25]^data[6]^CRC_temp[24]^data[7];
- CRC_temp[9]<=CRC_temp[1]^CRC_temp[29]^data[2]^CRC_temp[28]^data[3]^CRC_temp[26]^data[5]^CRC_temp[25]^data[6];
- CRC_temp[10]<=CRC_temp[2]^CRC_temp[29]^data[2]^CRC_temp[27]^data[4]^CRC_temp[26]^data[5]^CRC_temp[24]^data[7];
- CRC_temp[11]<=CRC_temp[3]^CRC_temp[28]^data[3]^CRC_temp[27]^data[4]^CRC_temp[25]^data[6]^CRC_temp[24]^data[7];
- CRC_temp[12]<=CRC_temp[4]^CRC_temp[29]^data[2]^CRC_temp[28]^data[3]^CRC_temp[26]^data[5]^CRC_temp[25]^data[6]^CRC_temp[24]^CRC_temp[30]^data[1]^data[7];
- CRC_temp[13]<=CRC_temp[5]^CRC_temp[30]^data[1]^CRC_temp[29]^data[2]^CRC_temp[27]^data[4]^CRC_temp[26]^data[5]^CRC_temp[25]^CRC_temp[31]^data[0]^data[6];
- CRC_temp[14]<=CRC_temp[6]^CRC_temp[31]^data[0]^CRC_temp[30]^data[1]^CRC_temp[28]^data[3]^CRC_temp[27]^data[4]^CRC_temp[26]^data[5];
- CRC_temp[15]<=CRC_temp[7]^CRC_temp[31]^data[0]^CRC_temp[29]^data[2]^CRC_temp[28]^data[3]^CRC_temp[27]^data[4];
- CRC_temp[16]<=CRC_temp[8]^CRC_temp[29]^data[2]^CRC_temp[28]^data[3]^CRC_temp[24]^data[7];
- CRC_temp[17]<=CRC_temp[9]^CRC_temp[30]^data[1]^CRC_temp[29]^data[2]^CRC_temp[25]^data[6];
- CRC_temp[18]<=CRC_temp[10]^CRC_temp[31]^data[0]^CRC_temp[30]^data[1]^CRC_temp[26]^data[5];
- CRC_temp[19]<=CRC_temp[11]^CRC_temp[31]^data[0]^CRC_temp[27]^data[4];
- CRC_temp[20]<=CRC_temp[12]^CRC_temp[28]^data[3];
- CRC_temp[21]<=CRC_temp[13]^CRC_temp[29]^data[2];
- CRC_temp[22]<=CRC_temp[14]^CRC_temp[24]^data[7];
- CRC_temp[23]<=CRC_temp[15]^CRC_temp[25]^data[6]^CRC_temp[24]^CRC_temp[30]^data[1]^data[7];
- CRC_temp[24]<=CRC_temp[16]^CRC_temp[26]^data[5]^CRC_temp[25]^CRC_temp[31]^data[0]^data[6];
- CRC_temp[25]<=CRC_temp[17]^CRC_temp[27]^data[4]^CRC_temp[26]^data[5];
- CRC_temp[26]<=CRC_temp[18]^CRC_temp[28]^data[3]^CRC_temp[27]^data[4]^CRC_temp[24]^CRC_temp[30]^data[1]^data[7];
- CRC_temp[27]<=CRC_temp[19]^CRC_temp[29]^data[2]^CRC_temp[28]^data[3]^CRC_temp[25]^CRC_temp[31]^data[0]^data[6];
- CRC_temp[28]<=CRC_temp[20]^CRC_temp[30]^data[1]^CRC_temp[29]^data[2]^CRC_temp[26]^data[5];
- CRC_temp[29]<=CRC_temp[21]^CRC_temp[31]^data[0]^CRC_temp[30]^data[1]^CRC_temp[27]^data[4];
- CRC_temp[30]<=CRC_temp[22]^CRC_temp[31]^data[0]^CRC_temp[28]^data[3];
- CRC_temp[31]<=CRC_temp[23]^CRC_temp[29]^data[2];
- end
- else if(CRC_read)
- CRC_temp <= {CRC_temp[23:0], 8'hff};
-
- endmodule
复制代码
3.1.4 PAUSE帧流控制模块
uimac_rx模块接收到PAUSE帧后,会将有效数据段送入mac_tx_frame_ctrl模块进行解析,得到暂停时间和PAUSE帧发送方的MAC地址,将其发送给uimac_tx模块的mac_tx_pause_ctrl子模块。
- /*******************************mac_tx_frame_ctrl模块*********************
- --以下是米联客设计的mac_tx_frame_ctrl模块,用于产生MAC发送模块的PAUSE暂停发送
- 1.
- *********************************************************************/
- `timescale 1ns/1ps
- module uimac_tx_frame_ctrl
- (
- input wire I_clk,
- input wire I_reset,
- input wire I_mac_pause_en,
- input wire [7:0] I_mac_data,
- output reg O_mac_pause_en,
- output reg [21:0] O_mac_pause_time//发送MAC停止发送数据的时间
- );
-
- reg [15:0] opcode;
- reg [15:0] pause_time;//pause_time字段为发送MAC停止发送数据的时间,每单位为512bit传输时间,比如数值为16’d1024表示暂停时间为MAC传输1024*512bit数据所需要的时间
- reg [2:0] cnt;
- reg STATE;
-
- localparam READ_FRAME = 0;
- localparam WAIT_FRAME_END = 1;
-
- localparam PAUSE_FRAME = 16'h0001;//操作码,固定值为0x0001
-
- always@(posedge I_clk or posedge I_reset) begin
- if(I_reset) begin
- cnt <= 3'd0;
- opcode <= 16'd0;
- pause_time <= 16'd0;
- O_mac_pause_en <= 1'b0;
- O_mac_pause_time <= 22'd0;
- STATE <= READ_FRAME;
- end
- else begin
- case(STATE)
- READ_FRAME:begin
- if(I_mac_pause_en)//帧流控制有效
- case(cnt)
- 0:begin opcode[15: 8] <= I_mac_data; cnt <= cnt + 1'b1;end
- 1:begin
- opcode[ 7: 0] <= I_mac_data;
- if({opcode[15: 8], I_mac_data} == PAUSE_FRAME) begin//判断是PAUSE帧
- STATE <= READ_FRAME;
- cnt <= cnt + 1'b1;
- end
- else begin
- STATE <= WAIT_FRAME_END;
- cnt <= 3'd0;
- end
- end
- 2:begin pause_time[15: 8] <= I_mac_data; cnt <= cnt + 1'b1;end
- 3:begin pause_time[ 7: 0] <= I_mac_data; cnt <= cnt + 1'b1;end//需要暂停发送的时间
- 4:begin
- cnt <= 3'd0;
- opcode <= 16'd0;
- pause_time <= 16'd0;
- O_mac_pause_en <= 1'b1;//通知MAC发送控制器,接收到了PAUSE帧
- O_mac_pause_time <= {pause_time, 6'd0};//*512/8 = *64 = *(2^6)
- STATE <= WAIT_FRAME_END;//等待帧结束
- end
- endcase
- else
- STATE <= READ_FRAME;
- end
- WAIT_FRAME_END:begin//等待帧结束
- O_mac_pause_time <= 22'd0;
- O_mac_pause_en <= 1'b0;
- if(I_mac_pause_en)
- STATE <= WAIT_FRAME_END;
- else
- STATE <= READ_FRAME;
- end
- endcase
- end
- end
-
- endmodule
复制代码
mac_tx_pause_ctrl接收到mac_pasue_en信号为高时,将接收的信息寄存,状态机跳转,等待MAC发送端发送完一帧数据,进入帧间隔等待。当MAC发送模块进入帧间隔状态后,流控模块拉高pause_flag信号,等待暂停时间结束后将信号拉低。
- /*******************************mac_tx_pause_ctrl模块*********************
- --以下是米联客设计的mac_tx_pause_ctrl MAC发送端,流控制器模块
- 1.
- *********************************************************************/
- `timescale 1ns/1ps
- module uimac_tx_pause_ctrl
- (
- input wire I_clk,
- input wire I_reset,
- input wire [2:0] I_mac_state,
- input wire I_mac_pause_en,
- input wire [21:0] I_mac_pause_time,
- input wire [47:0] I_mac_pause_addr,
- output reg [47:0] O_pause_dst_mac_addr,
- output reg O_pause_flag
- );
-
- reg [21:0] pause_clk_num;
- reg [21:0] pause_clk_cnt;
- reg [1:0] STATE;
-
- localparam WAIT_PAUSE_FRAME = 2'd0;
- localparam WAIT_CURRENT_SEND_DONE = 2'd1;
- localparam MAC_SEND_PAUSE = 2'd2;
-
- localparam ADD_IFG = 3'd4;
-
- always@(posedge I_clk or posedge I_reset) begin
- if(I_reset) begin
- pause_clk_num <= 22'd0;
- pause_clk_cnt <= 22'd0;
- O_pause_flag <= 1'b0;
- O_pause_dst_mac_addr <= 48'd0;
- STATE <= WAIT_PAUSE_FRAME;
- end
- else begin
- case(STATE)
- WAIT_PAUSE_FRAME:begin//等待PAUSE帧
- O_pause_flag <= 1'b0;
- if(I_mac_pause_en) begin //MAC接收模块接收到PAUSE帧
- O_pause_dst_mac_addr <= I_mac_pause_addr;//MAC发送模块需要发送PAUSE的目的MAC地址
- pause_clk_num <= I_mac_pause_time;//PAUSE时间,在MAC接收端已经换算好需要PAUSE的时钟周期个数
- STATE <= WAIT_CURRENT_SEND_DONE;
- end
- else begin
- O_pause_dst_mac_addr <= 48'd0;
- pause_clk_num <= 22'd0;
- STATE <= WAIT_PAUSE_FRAME;
- end
- end
- WAIT_CURRENT_SEND_DONE:begin//等待当MAC发送状态机在I_mac_state == ADD_IFG状态的时候,设置O_pause_flag标志
- if(I_mac_state == ADD_IFG) begin
- O_pause_flag <= 1'b1;//设置O_pause_flag,通知MAC 帧发送模块,暂停数据发送
- STATE <= MAC_SEND_PAUSE;
- end
- else begin
- O_pause_flag <= 1'b0;
- STATE <= WAIT_CURRENT_SEND_DONE;
- end
- end
- MAC_SEND_PAUSE:begin//暂停数据发送,等待(pause_clk_num - 3)个时钟周期
- if(pause_clk_cnt == (pause_clk_num - 3)) begin
- O_pause_flag <= 1'b0;
- O_pause_dst_mac_addr <= 48'd0;
- pause_clk_cnt <= 22'd0;
- pause_clk_num <= 22'd0;
- STATE <= WAIT_PAUSE_FRAME;
- end
- else begin
- O_pause_flag <= 1'b1;//设置O_pause_flag,通知MAC 帧发送模块,暂停数据发送
- pause_clk_cnt <= pause_clk_cnt + 1'b1;
- STATE <= MAC_SEND_PAUSE;
- end
- end
- endcase
- end
- end
-
- endmodule
复制代码
3.2 IP_ARP层由于IP和ARP数据包送至MAC层要经过同一个通道,需要对发送的数据包类型进行判断和仲裁,这就需要额外增加一个IP_ARP层。
3.2.1 IP_ARP接收模块
该模块接收到MAC帧经过MAC层解包得到的数据包,通过类型字段判断该包是IP包还是ARP包,将其送入对应的模块中处理。
- /*******************************uiip_arp_rx模块*********************
- --以下是米联客设计的uiip_arp_rx模块
- 1.该模块1用于区分接收数据是IP包还是ARP包
- *********************************************************************/
- `timescale 1ns/1ps
- module uiip_arp_rx
- (
- input wire I_ip_arp_reset, //复位
- input wire I_ip_arp_rclk, //RX 接收时钟
- output wire O_ip_rvalid, //接收的有效IP信号
- output wire [7:0] O_ip_rdata, //接收的IP数据
- output wire O_arp_rvalid, //接收的有效ARP信号
- output wire [7:0] O_arp_rdata, //接收的有效ARP数据
-
- input wire I_mac_rvalid, //MAC接收到的数据有效信号
- input wire [7:0] I_mac_rdata, //MAC接收的有效数据
- input wire [15:0] I_mac_rdata_type //MAC接收到的帧类型
-
- );
- reg ip_rx_data_valid; //接收的有效IP信号
- reg [7:0] ip_rx_data; //接收的IP数据
- reg arp_rx_data_valid; //接收的有效ARP信号
- reg [7:0] arp_rx_data; //接收的有效ARP数据
-
- assign O_ip_rvalid = ip_rx_data_valid;
- assign O_ip_rdata = ip_rx_data;
- assign O_arp_rvalid = arp_rx_data_valid;
- assign O_arp_rdata = arp_rx_data;
-
- localparam ARP_TYPE = 16'h0806; //ARP包类型
- localparam IP_TYPE = 16'h0800; //IP 包类型
- always@(posedge I_ip_arp_rclk or posedge I_ip_arp_reset) begin
- if(I_ip_arp_reset) begin
- ip_rx_data_valid <= 1'b0;
- ip_rx_data <= 8'd0;
- arp_rx_data_valid <= 1'b0;
- arp_rx_data <= 8'd0;
- end
- else if(I_mac_rvalid) begin
- if(I_mac_rdata_type == IP_TYPE) begin//IP包
- ip_rx_data_valid <= 1'b1;
- ip_rx_data <= I_mac_rdata;
- end
- else if(I_mac_rdata_type == ARP_TYPE) begin//ARP包
- arp_rx_data_valid <= 1'b1;
- arp_rx_data <= I_mac_rdata;
- end
- else begin
- ip_rx_data_valid <= 1'b0;
- ip_rx_data <= 8'd0;
- arp_rx_data_valid <= 1'b0;
- arp_rx_data <= 8'd0;
- end
- end
- else begin
- ip_rx_data_valid <= 1'b0;
- ip_rx_data <= 8'd0;
- arp_rx_data_valid <= 1'b0;
- arp_rx_data <= 8'd0;
- end
- end
-
- endmodule
复制代码
3.2.2 IP_ARP发送模块
该模块接收IP层和ARP层传来的发送请求,通过busy信号与上层协议模块进行握手,来发送对应的数据。该模块的状态机转换图如图所示。
IDLE:如果是IP层发送的请求,且arp_req_pend信号没有挂起时,进入CHECK_MAC_CACHEE状态,进入ARP层查询mac_cache中缓存的MAC地址。如果是ARP层发送的请求,在MAC层非忙时,将arp_tbusy拉高,表示可以发送ARP包,进入WAIT_ARP_PACKET状态。
CHECK_MAC_CACHE:若没有查询到IP地址对应的MAC地址,则使能O_arp_treq_en信号,请求ARP层发送ARP广播包,并且将arp_req_pend挂起(该信号挂起时不能发送IP包),回到IDLE状态,等待ARP层发送请求信号。若查询到MAC地址,进入WAIT_IP_PACKET状态,等待IP层将有效数据发送过来。
WAIT_IP_PACKET:若I_ip_valid拉高,说明IP包有效数据开始传入,接收传来的数据并将其发送至MAC层,进入SEND_IP_PACKET状态。
SEND_IP_PACKET:等待一帧IP包数据全部发送完成时,回到IDLE状态。
WAIT_ARP_PACKET:I_arp_valid拉高,说明ARP包有效数据开始传入,接收传来的数据并将其发送至MAC层,进入SEND_ARP_PACKET状态。
SEND_ARP_PACKET:如果发送的数据包是ARP应答包,数据全部发送完成时,回到IDLE状态。如果发送的数据包是ARP请求包,则要进入SEND_ARP_REPLY状态,等待接收到对方发送ARP应答包。
SEND_ARP_REPLY:接收到ARP应答包后,将arp_req_pend信号拉低,回到IDLE状态。若超时未收到应答,则回到IDLE状态,此时由于arp_req_pend信号一直为高,该模块会持续发送ARP请求直至收到应答。
复制代码
3.3 IP层ICMP层数据和UDP层数据都要经过IP层打包或者解包,IP层主要功能为判断数据报文类型,进行IP首部校验,添加包头或者过滤包头,处理ICMP请求。
3.3.1 IP接收模块该模块的主要功能是接收uiip_arp_rx传入的数据包,通过首部校验判断包头的正确性,对包头进行过滤,并且提取出UDP报文和ICMP报文。
该模块的状态机转换图如图所示。
WAIT_IP_PACKET:等待接收IP包,若I_ip_rvalid拉高,代表数据传入,进入RECORD_IP_HEADER状态。
RECORD_IP_HEADER:接收包头信息,20字节的包头数据全部接收完时,判断收到的IP地址和本地IP地址是否匹配,若不匹配,丢弃该帧,进入WAIT_PACKET_END状态;若匹配,则判断数据包类型。如果收到的数据报文为UDP数据报文,进入OUTPUT_UDP_PACKET状态。如果收到的数据报文为ICMP报文,则将数据传入ICMP子层模块,等待一包数据传输完成后,进入WAIT_PACKET_END状态。
OUTPUT_UDP_PACKET:将有效数据打拍后传输至UDP层,一包数据传输完成后,回到WAIT_IP_PACKET状态。
WAIT_PACKET_END:等待数据有效信号拉低,一帧数据传输完成,回到WAIT_IP_PACKET状态。
复制代码
3.3.2 IP发送模块该模块的主要功能有:接收uiudp_tx发送的UDP报文和uiip_rx发送的ICMP报文信息,将其封装成IP包并发送至下层uiip_arp_tx模块。
该模块的状态机转换图如图所示。
IDLE:uiip_tx模块处于发送空闲时,接收ICMP和UDP报文发送请求,优先响应ICMP请求。当收到请求时,向uiip_arp_tx模块发送请求信号,通过udp_pkg_en信号标记报文类型,进入WAIT_ACK状态,等待握手响应信号。
WAIT_ACK:接收到I_ip_tbusy信号,代表和IP_ARP层模块握手成功,可以发送数据,进入SEND_IP_HEADER状态。若要发送的是UDP报文,则会发送O_ip_udp_tbusy信号和UDP层进行握手。
SEND_IP_HEADER:在有效数据报文前添加对应的IP包头,发送包头完毕后,若报文类型为UDP报文,进入SEND_UDP_PACKET状态;若为ICMP报文,则进入SEND_ICMP_PACKET状态。
SEND_UDP_PACKET和SEND_ICMP_PACKET:发送完报文中的有效数据后,将标识加1,回到IDLE状态。
复制代码
3.3.3 IP首部校验模块
正常计算IP首部校验和时,将校验位置0。由于每两字节数据相加得到的不再产生进位结果,为最终校验和的取反,所以当计算过程中加入校验位时,相加得到的结果应当为全1。若结果不为全1,则数据校验错误,将O_check_rerror信号置为高。
- `timescale 1ns / 1ps
-
- module ip_header_checksum(
- input wire I_clk,
- input wire I_reset,
- input wire I_ip_rdata_valid,
- input wire [7:0] I_ip_rdata,
- output wire O_checksum_rerror
- );
- reg checksum_correct;
- assign O_checksum_rerror = ~checksum_correct;
-
- reg [1:0] state;
- reg [3:0] cnt;
- wire [16:0] tmp_accum1;
- reg [15:0] accum1, accum2;
-
- assign tmp_accum1 = accum1 + accum2;
-
- always @(posedge I_clk or posedge I_reset) begin
- if(I_reset) begin
- state <= 2'd0;
- cnt <= 4'd0;
- accum1 <= 16'd0;
- accum2 <= 16'd0;
- checksum_correct <= 1'b1;
- end
- else begin
- case(state)
- 0: begin
- if(I_ip_rdata_valid) begin
- accum1[15:8] <= I_ip_rdata;
- state <= 2'd1;
- end
- else begin
- accum1[15:8] <= 8'd0;
- state <= 2'd0;
- end
- end
- 1: begin accum1[7:0] <= I_ip_rdata; state <= 2'd2; end
- 2: begin
- if(cnt == 4'd9) begin
- if((tmp_accum1[15:0] + tmp_accum1[16]) != 16'hffff)
- checksum_correct <= 1'b0;
- cnt <= 4'd0;
- state <= 2'd3;
- end
- else begin
- accum2 <= tmp_accum1[15:0] + tmp_accum1[16];
- accum1[15:8] <= I_ip_rdata;
- cnt <= cnt + 1'b1;
- state <= 2'd1;
- end
- end
- 3: begin
- accum1 <= 16'd0;
- accum2 <= 16'd0;
- if(I_ip_rdata_valid)
- state <= state;
- else
- state <= 2'd0;
- end
- endcase
- end
- end
- endmodule
复制代码
3.4 ARP层该层具有接收ARP请求、发送ARP回复,和发送ARP请求、接收ARP回复的功能,并将接收到的对端的地址信息存入cache中。
3.4.1 ARP接收模块该模块通过计数器接收28字节ARP信息字段。信息接收完成后,通过接收到的操作码判断接收到的数据包是ARP请求包还是ARP应答包。如果是应答包,将接收到的地址信息写入cache中;如果是请求包,判断目的IP地址与本地IP地址是否一致,若一致,将地址信息写入cache中,且发送至uiarp_rx模块,若不一致,则过滤该包。
- if(OPER == ARP_REQUEST) begin//如果是ARP请求 ARP_REQUEST = 16'h0001(16’h01 ARP请求包 ; 16’h02 ARP应答)
- if(TPA == I_ip_local_addr) begin //比较接收到的ARP包里面的IP地址是否和本地IP地址一致
- O_arp_req_ip_addr <= SPA; //发送IP(远端源IP地址)
- O_arp_req_mac_addr <= SHA; //发送方MAC(远端源地址MAC)
- O_arp_req_valid <= 1'b1; //设置ARP请求有效(通知发送ARP发送模块发送一个ARP应答给远端主机),保存远端主机的IP地址和MAC地址到cache
- O_arp_reply_done <= 1'b0;
- end
- else begin
- O_arp_req_ip_addr <= 32'd0;
- O_arp_req_mac_addr <= 48'd0;
- O_arp_req_valid <= 1'b0;
- O_arp_reply_done <= 1'b0;
- end
- end
复制代码
3.4.2 ARP发送模块该模块对ARP应答和ARP请求同时进行处理,首先对这两种逻辑进行仲裁,以避免发送冲突。如果既没有ARP应答也没有ARP请求,且内部ARP包发送逻辑非忙,则处理缓存的有效信号;如果只有ARP应答,没有ARP请求,且发送逻辑非忙,则请求发送ARP应答包,若发送逻辑忙,则将发送ARP应答包的请求缓存至reply_buffer_valid中,等待非忙时发出;如果只有ARP请求,没有ARP应答,且发送逻辑非忙,则请求发送ARP请求包,若发送逻辑忙,则将发送ARP请求包的请求缓存至request_buffer_valid中,等待非忙时发出;若ARP应答和ARP请求同时为高,则优先发送ARP请求,缓存ARP应答。
- case({I_arp_treq_en, I_arp_rreply_en})//本状态机实现了即便是同时有ARP应答或者ARP请求,都能确保完成发送
- 2'b00:begin
- if((!O_arp_treq) && (!O_arp_tvalid)) begin//没有arp_treq请求,并且arp_tvalid为0 代表没有要发送的ARP数据
- if(request_buffer_valid) begin//如果有未发送完的ARP请求,则继续发送
- OPER <= ARP_REQUEST;
- TPA <= ip_request_buffer;
- THA <= 48'd0;
- request_buffer_valid <= 1'b0;//清除request_buffer_valid
- O_arp_treq <= 1'b1;
- end
- else if(reply_buffer_valid) begin//如果有未发送完的ARP应答,则继续发送
- OPER <= ARP_REPLY;
- TPA <= ip_reply_buffer;
- THA <= mac_reply_buffer;
- reply_buffer_valid <= 1'b0;//清除request_buffer_valid
- O_arp_treq <= 1'b1;
- end
- end
- end
- 2'b01:begin//发送ARP应答
- if((!O_arp_treq) && (!O_arp_tvalid)) begin
- OPER <= ARP_REPLY;
- TPA <= I_arp_rreply_ip_addr;
- THA <= I_arp_rreply_mac_addr;
- O_arp_treq <= 1'b1;
- end
- else begin//需要arp应答
- ip_reply_buffer <= I_arp_rreply_ip_addr;//寄存目的地址IP
- mac_reply_buffer <= I_arp_rreply_mac_addr;//寄存目的地址MAC
- reply_buffer_valid <= 1'b1;//需要发送ARP应答
- end
- end
- 2'b10:begin//发送ARP请求,当ip_arp_tx发送IP包查询MAC没有查询到,执行ARP请求,请求远程主机提供MAC
- if((!O_arp_treq) && (!O_arp_tvalid)) begin
- OPER <= ARP_REQUEST;
- TPA <= I_arp_tip_addr;
- THA <= 48'd0;
- O_arp_treq <= 1'b1;//ARP 发送
- end
- else begin//arp请求包
- ip_request_buffer <= I_arp_tip_addr;
- request_buffer_valid <= 1'b1;//ARP 请求有效标志
- end
- end
- 2'b11:begin//既有ARP请求,又有ARP应答
- if((!O_arp_treq) && (!O_arp_tvalid)) begin
- OPER <= ARP_REQUEST;
- TPA <= I_arp_tip_addr;
- THA <= 48'd0;
- O_arp_treq <= 1'b1;//ARP 发送
- end
- else begin
- ip_request_buffer <= I_arp_tip_addr;
- request_buffer_valid <= 1'b1;//ARP请求有效
- end
- ip_reply_buffer <= I_arp_rreply_ip_addr;
- mac_reply_buffer <= I_arp_rreply_mac_addr;
- reply_buffer_valid <= 1'b1; //ARP应答有效
- end
- endcase
复制代码
若O_arp_treq和uiip_arp_tx模块发送的I_arp_tbusy完成握手,则开始发送ARP包,并且通过判断ARP包的类型来确定发送的目的地址。通过计数器cnt发送28字节有效数据,再通过计数器pad_cnt在末尾补18个字节的0,即完成发送,开始等待下一次握手。
- case(STATE)
- WAIT_BUFFER_READY:begin
- if(O_arp_treq && I_arp_tbusy) begin
- O_arp_tdata <= HTYPE[15:8]; //硬件类型-以太网类型
- O_arp_tvalid <= 1'b1; //ARP数据有效
- cnt <= cnt + 1'b1;
- if(OPER == ARP_REQUEST) begin //如果是ARP请求
- O_arp_tdest_mac_addr <= 48'hff_ff_ff_ff_ff_ff; //ARP目的地址为广播地址
- O_arp_ttype <= 1'b1; //通知ip_arp_tx ARP类型为ARP请求
- end
- else begin
- O_arp_tdest_mac_addr <= THA;
- O_arp_ttype <= 1'b0; //通知ip_arp_tx ARP类型为ARP应答
- end
- O_arp_treq <= 1'b0;
- STATE <= SEND_ARP_PACKET;
- end
- else
- STATE <= WAIT_BUFFER_READY;
- end
- SEND_ARP_PACKET:begin
- case(cnt)
- 1: begin O_arp_tdata <= HTYPE[7:0]; cnt <= cnt + 1'b1;end
- 2: begin O_arp_tdata <= PTYPE[15:8]; cnt <= cnt + 1'b1;end
- 3: begin O_arp_tdata <= PTYPE[7:0]; cnt <= cnt + 1'b1;end
- 4: begin O_arp_tdata <= HLEN; cnt <= cnt + 1'b1;end
- 5: begin O_arp_tdata <= PLEN; cnt <= cnt + 1'b1;end
- 6: begin O_arp_tdata <= OPER[15:8]; cnt <= cnt + 1'b1;end
- 7: begin O_arp_tdata <= OPER[7:0]; cnt <= cnt + 1'b1;end
- 8: begin O_arp_tdata <= I_mac_local_addr[47:40]; cnt <= cnt + 1'b1;end
- 9: begin O_arp_tdata <= I_mac_local_addr[39:32]; cnt <= cnt + 1'b1;end
- 10: begin O_arp_tdata <= I_mac_local_addr[31:24]; cnt <= cnt + 1'b1;end
- 11: begin O_arp_tdata <= I_mac_local_addr[23:16]; cnt <= cnt + 1'b1;end
- 12: begin O_arp_tdata <= I_mac_local_addr[15:8]; cnt <= cnt + 1'b1;end
- 13: begin O_arp_tdata <= I_mac_local_addr[7:0]; cnt <= cnt + 1'b1;end
- 14: begin O_arp_tdata <= I_ip_local_addr[31:24]; cnt <= cnt + 1'b1;end
- 15: begin O_arp_tdata <= I_ip_local_addr[23:16]; cnt <= cnt + 1'b1;end
- 16: begin O_arp_tdata <= I_ip_local_addr[15:8]; cnt <= cnt + 1'b1;end
- 17: begin O_arp_tdata <= I_ip_local_addr[7:0]; cnt <= cnt + 1'b1;end
- 18: begin O_arp_tdata <= THA[47:40]; cnt <= cnt + 1'b1;end
- 19: begin O_arp_tdata <= THA[39:32]; cnt <= cnt + 1'b1;end
- 20: begin O_arp_tdata <= THA[31:24]; cnt <= cnt + 1'b1;end
- 21: begin O_arp_tdata <= THA[23:16]; cnt <= cnt + 1'b1;end
- 22: begin O_arp_tdata <= THA[15:8]; cnt <= cnt + 1'b1;end
- 23: begin O_arp_tdata <= THA[7:0]; cnt <= cnt + 1'b1;end
- 24: begin O_arp_tdata <= TPA[31:24]; cnt <= cnt + 1'b1;end
- 25: begin O_arp_tdata <= TPA[23:16]; cnt <= cnt + 1'b1;end
- 26: begin O_arp_tdata <= TPA[15:8]; cnt <= cnt + 1'b1;end
- 27: begin O_arp_tdata <= TPA[7:0]; cnt <= cnt + 1'b1;end
- 28: begin
- O_arp_tdata <= 8'd0;
- if(pad_cnt == 5'd17) begin //通过在末尾添加0以确保数据长度为46
- cnt <= cnt + 1'b1;
- pad_cnt <= 5'd0;
- end
- else begin
- cnt <= cnt;
- pad_cnt <= pad_cnt + 1'b1;
- end
- end
- 29: begin
- O_arp_tdata <= 8'd0;
- O_arp_tvalid <= 1'b0;
- O_arp_tdest_mac_addr <= 48'd0;
- O_arp_ttype <= 1'b0;
- cnt <= 5'd0;
- STATE <= WAIT_BUFFER_READY;
- end
- default:begin
- O_arp_tdata <= 8'd0;
- O_arp_tvalid <= 1'b0;
- cnt <= 5'd0;
- STATE <= WAIT_BUFFER_READY;
- end
- endcase
- end
- endcase
- end
- end
复制代码
3.4.3 CACHE模块
mac_cache模块相当于一个RAM,通过IP作为地址,写入或读出MAC地址。写入mac_cache模块的地址信息远程主机发送的ARP应答包或ARP请求包中的地址,在IP层发送UDP数据报文时,需要在mac_cache中读出和IP地址对应的MAC地址。
- `timescale 1ns/1ps
- module mac_cache
- (
- input wire I_wclk,
- input wire I_reset,
- input wire I_wen,
- input wire [31:0] I_wip_addr,
- input wire [47:0] I_wmac_addr,
-
- input wire I_rclk,
- input wire I_ren,
- input wire [31:0] I_rip_addr,
- output reg [47:0] O_rmac_addr,
- output reg O_rmac_done
- );
- reg mac_cache_flag [0:3];
- reg [31:0] ip_address_cache [0:3];
- reg [47:0] mac_address_cache [0:3];
- reg [1:0] index;
-
- always@(posedge I_wclk or posedge I_reset) begin
- if(I_reset) begin
- mac_cache_flag[0] <= 1'b0;
- ip_address_cache[0] <= 32'd0;
- mac_address_cache[0] <= 48'd0;
- mac_cache_flag[1] <= 1'b0;
- ip_address_cache[1] <= 32'd0;
- mac_address_cache[1] <= 48'd0;
- mac_cache_flag[2] <= 1'b0;
- ip_address_cache[2] <= 32'd0;
- mac_address_cache[2] <= 48'd0;
- mac_cache_flag[3] <= 1'b0;
- ip_address_cache[3] <= 32'd0;
- mac_address_cache[3] <= 48'd0;
- index <= 2'd0;
- end
- else begin
- if(I_wen) begin
- if(mac_cache_flag[0] && ip_address_cache[0] == I_wip_addr)
- mac_address_cache[0] <= I_wmac_addr;
- else if(mac_cache_flag[1] && ip_address_cache[1] == I_wip_addr)
- mac_address_cache[1] <= I_wmac_addr;
- else if(mac_cache_flag[2] && ip_address_cache[2] == I_wip_addr)
- mac_address_cache[2] <= I_wmac_addr;
- else if(mac_cache_flag[3] && ip_address_cache[3] == I_wip_addr)
- mac_address_cache[3] <= I_wmac_addr;
- else begin
- mac_cache_flag[index] <= 1'b1;
- ip_address_cache[index] <= I_wip_addr;
- mac_address_cache[index] <= I_wmac_addr;
- index <= index + 1'b1;
- end
- end
- else begin
- mac_cache_flag[0] <= mac_cache_flag[0];
- ip_address_cache[0] <= ip_address_cache[0];
- mac_address_cache[0] <= mac_address_cache[0];
- mac_cache_flag[1] <= mac_cache_flag[1];
- ip_address_cache[1] <= ip_address_cache[1];
- mac_address_cache[1] <= mac_address_cache[1];
- mac_cache_flag[2] <= mac_cache_flag[2];
- ip_address_cache[2] <= ip_address_cache[2];
- mac_address_cache[2] <= mac_address_cache[2];
- mac_cache_flag[3] <= mac_cache_flag[3];
- ip_address_cache[3] <= ip_address_cache[3];
- mac_address_cache[3] <= mac_address_cache[3];
- end
- end
- end
-
- always@(posedge I_rclk or posedge I_reset) begin
- if(I_reset) begin
- O_rmac_addr <= 48'd0;
- O_rmac_done <= 1'b0;
- end
- else begin
- if(I_ren) begin
- O_rmac_done <= 1'b1;
- if(mac_cache_flag[0] && I_rip_addr == ip_address_cache[0])
- O_rmac_addr <= mac_address_cache[0];
- else if(mac_cache_flag[1] && I_rip_addr == ip_address_cache[1])
- O_rmac_addr <= mac_address_cache[1];
- else if(mac_cache_flag[2] && I_rip_addr == ip_address_cache[2])
- O_rmac_addr <= mac_address_cache[2];
- else if(mac_cache_flag[3] && I_rip_addr == ip_address_cache[3])
- O_rmac_addr <= mac_address_cache[3];
- else
- O_rmac_addr <= 48'd0;
- end
- else begin
- O_rmac_addr <= O_rmac_addr;
- O_rmac_done <= 1'b0;
- end
- end
- end
- endmodule
复制代码
3.5 UDP层该层实现用户数据和UDP报文的互转,相比于其它层次的设计,该层的逻辑相对简单。
3.5.1 UDP接收模块
通过计数器将UDP报文头部信息拆解,提取出端口号和长度信息。通过提取的长度对有效数据进行计数,从而得到有效数据并发送给用户端。
- always@(posedge I_R_udp_clk or posedge I_reset) begin
- if(I_reset) begin
- cnt <= 4'd0;
- O_R_udp_valid <= 1'b0;
- O_R_udp_data <= 8'd0;
- O_R_udp_len <= 16'd0;
- udp_src_port <= 16'd0;
- udp_dest_port <= 16'd0;
- udp_pkg_len <= 16'd0;
- udp_data_cnt <= 16'd0;
- end
- else if(I_udp_ip_rvalid) begin
- udp_data_cnt <= udp_data_cnt + 1'b1;
- case(cnt)
- 0 :begin udp_src_port[15:8] <= I_udp_ip_rdata; cnt <= cnt + 1'b1;end //UDP接收源端口(远程主机端口)
- 1 :begin udp_src_port[7:0] <= I_udp_ip_rdata; cnt <= cnt + 1'b1;end //UDP接收源端口(远程主机端口)
- 2 :begin udp_dest_port[15:8] <= I_udp_ip_rdata; cnt <= cnt + 1'b1;end //UDP接收目的端口(本地主机端口)
- 3 :begin udp_dest_port[7:0] <= I_udp_ip_rdata; cnt <= cnt + 1'b1;end //UDP接收目的端口(本地主机端口)
- 4 :begin udp_pkg_len[15:8] <= I_udp_ip_rdata; cnt <= cnt + 1'b1;end //UDP数据包长度
- 5 :begin udp_pkg_len[7:0] <= I_udp_ip_rdata; cnt <= cnt + 1'b1;end //UDP数据包长度
- 6 :begin cnt <= cnt + 1'b1;end //跳过检验和
- 7 :begin cnt <= cnt + 1'b1;end //跳过校验和
- 8 :begin
- O_R_udp_valid <= 1'b1; //UDP接收数据有效
- O_R_udp_data <= I_udp_ip_rdata;
- O_R_udp_len <= udp_pkg_len - 16'd8;
- cnt <= cnt + 1'b1;
- end
- 9 :begin
- if(udp_pkg_len < 16'd26) begin
- if(udp_data_cnt == udp_pkg_len) begin
- O_R_udp_valid <= 1'b0;
- O_R_udp_data <= 8'd0;
- cnt <= cnt + 1'b1;
- end
- else begin
- O_R_udp_valid <= 1'b1;
- O_R_udp_data <= I_udp_ip_rdata;
- cnt <= cnt;
- end
- end
- else begin
- O_R_udp_valid <= 1'b1;
- O_R_udp_data <= I_udp_ip_rdata;
- cnt <= cnt;
- end
- end
- 10 :begin
- O_R_udp_valid <= 1'b0;
- O_R_udp_data <= 8'd0;
- cnt <= cnt;
- end
- default: cnt <= 4'd0;
- endcase
- end
- else if(!I_udp_ip_rvalid) begin
- udp_pkg_len <= 16'd0;
- udp_src_port <= 16'd0;
- udp_dest_port <= 16'd0;
- udp_data_cnt <= 16'd0;
- O_R_udp_len <= 16'd0;
- O_R_udp_data <= 8'd0;
- O_R_udp_valid <= 1'b0;
- cnt <= 4'd0;
- end
- end
复制代码
3.5.2 UDP发送模块
状态机转换图如下:
IDLE:当用户端发送写请求,且uiip_tx模块不忙时,进入WAIT_ACK状态。
WAIT_ACK:等待uiip_tx模块将udp_ip_tbusy信号拉高,完成握手,进入SEND_UDP_HEADER状态,开始发送UDP报文头部。
SEND_UDP_HEADER:将8个字节的UDP报文头部添加在有效数据之前,头部数据发送完成后,进入SEND_UDP_PACKET状态。
SEND_UDP_PACKET:将经过shift_ram移位8位的数据拼接在包头之后,通过计数器判断发送数据的数量,待数据发送完成后,回到IDLE状态,等待下一次发送请求。
- always@(posedge I_W_udp_clk or posedge I_reset) begin
- if(I_reset) begin
- cnt <= 4'd0;
- O_udp_ip_tvalid <= 1'b0;
- O_udp_ip_tdata <= 8'd0;
- O_udp_ip_tpkg_len <= 16'd0;
- trans_data_cnt <= 16'd0;
- STATE <= IDLE;
- end
- else begin
- case(STATE)
- IDLE:begin
- if(I_W_udp_req & (~I_udp_ip_tbusy)) //当有写UDP请求,并且ip_tx模块不忙(当I_udp_ip_tbusy=1代表正在ip层正在传输数据)
- STATE <= WAIT_ACK; //进入WAIT_ACK
- else
- STATE <= IDLE;
- end
- WAIT_ACK:begin
- if(I_udp_ip_tbusy) //如果ip_tx模块准备好,代表udp_layer可以发送数据
- STATE <= SEND_UDP_HEADER;
- else
- STATE <= WAIT_ACK;
- end
- SEND_UDP_HEADER:begin
- case(cnt)
- 0 :begin
- if(I_W_udp_valid) begin
- O_udp_ip_tvalid <= 1'b1; //udp包数据有效
- O_udp_ip_tdata <= I_udp_local_port[15:8]; //UDP报文源端口
- O_udp_ip_tpkg_len <= I_W_udp_len + 16'h0008; //UDP报文长度,其中8bytes为udp首部
-
- cnt <= cnt + 1'b1;
- end
- else
- cnt <= 0;
- end
- 1 :begin
- O_udp_ip_tdata <= I_udp_local_port[7:0]; //UDP报文源端口
- cnt <= cnt + 1'b1;
- end
- 2 :begin
- O_udp_ip_tdata <= I_udp_dest_port[15:8]; //UDP报文目的端口
- cnt <= cnt + 1'b1;
- end
- 3 :begin
- O_udp_ip_tdata <= I_udp_dest_port[7:0]; //UDP报文目的端口
- cnt <= cnt + 1'b1;
- end
- 4 :begin
- O_udp_ip_tdata <= O_udp_ip_tpkg_len[15:8];//UDP报文长度
- cnt <= cnt + 1'b1;
- end
- 5 :begin
- O_udp_ip_tdata <= O_udp_ip_tpkg_len[7:0]; //UDP报文长度
- cnt <= cnt + 1'b1;
- end
- 6 :begin
- O_udp_ip_tdata <= CHECKSUM[7:0]; //校验和
- cnt <= cnt + 1'b1;
- end
- 7 :begin
- O_udp_ip_tdata <= CHECKSUM [7:0]; //校验和
- cnt <= 0;
- STATE <= SEND_UDP_PACKET;
- end
- default: cnt <= 0;
- endcase
- end
- SEND_UDP_PACKET:begin
- if(trans_data_cnt != (O_udp_ip_tpkg_len - 16'd8)) begin
- O_udp_ip_tvalid <= 1'b1;
- O_udp_ip_tdata <= shift_data_out;
- trans_data_cnt <= trans_data_cnt + 1'b1;
- STATE <= SEND_UDP_PACKET;
- end
- else begin
- trans_data_cnt <= 16'd0;
- O_udp_ip_tdata <= 8'd0;
- O_udp_ip_tvalid <= 1'b0;
- O_udp_ip_tpkg_len <= 16'd0;
- cnt <= 0;
- STATE <= IDLE;
- end
- end
- default: STATE <= IDLE;
- endcase
- end
- end
复制代码
3.6 ICMP层该层在程序中为IP层的子层,设计了接收ICMP请求包并回复的功能。
3.6.1 ICMP接收模块该模块首先过滤掉ICMP报文头部,并且对头部数据进行校验,校验方式和IP首部校验相同。
- //ICMP报文的校验和
- always@(posedge I_clk or posedge I_reset) begin
- if(I_reset) begin
- accum1 <= 16'd0;
- accum2 <= 32'd0;
- checksum_state <= 2'd0;
- checksum_correct <= 1'b1;
- end
- else begin
- case(checksum_state)
- 0:begin
- if(I_icmp_pkg_valid) begin
- accum1[15:8] <= I_icmp_pkg_data;
- checksum_state <= 2'd1;
- end
- else begin
- accum1[15:8] <= 8'd0;
- checksum_state <= 2'd0;
- end
- end
- 1:begin
- accum1[7:0] <= I_icmp_pkg_data;
- checksum_state <= 2'd2;
- end
- 2:begin
- if(!I_icmp_pkg_valid) begin
- checksum_state <= 2'd3;
- if((tmp_accum1[15:0] + tmp_accum1[31:16]) != 16'hffff)
- checksum_correct <= 1'b0;
- else
- checksum_correct <= 1'b1;
- end
- else begin
- accum2 <= tmp_accum1;
- accum1[15:8] <= I_icmp_pkg_data;
- checksum_state <= 2'd1;
- end
- end
- 3:begin
- accum1 <= 16'd0;
- accum2 <= 32'd0;
- checksum_state <= 2'd0;
- end
- endcase
- end
- end
复制代码
将报文的附加数据存入FIFO中,并向icmp_pkg_tx模块发送ping应答信息,请求发送ping应答包。
- //以下模块完成ICMP报文包echo ping应答的请求,并且先缓存到ip_layer的FIFO中
- always@(posedge I_clk or posedge I_reset) begin
- if(I_reset) begin
- cnt <= 4'd0;
- type1 <= 8'd0;
- code <= 8'd0;
- echo_data_cnt <= 10'd0;
- checksum <= 16'd0;
- O_icmp_req_en <= 1'b0;
- O_icmp_req_id <= 16'd0;
- O_icmp_req_sq_num <= 16'd0;
- O_icmp_ping_echo_data_valid <= 1'b0;
- O_icmp_ping_echo_data <= 8'd0;
- O_icmp_req_checksum <= 16'd0;
- STATE <= RECORD_ICMP_HEADER;
- end
- else begin
- case(STATE)
- RECORD_ICMP_HEADER:begin
- O_icmp_req_en <= 1'b0;
- echo_data_cnt <= 10'd0;
- if(I_icmp_pkg_valid) //ICMP报文有效
- case(cnt)
- 0: begin type1 <= I_icmp_pkg_data; cnt <= cnt + 1'b1;end //ICMP报文首部-类型:8位数表示错误类型的差错报文或者查询类型的报告报文,一般是查询报文(0代表回显应答(ping应答);1代表查
- 1: begin code <= I_icmp_pkg_data; cnt <= cnt + 1'b1;end //ICMP报文首部-类型:代码占用8位数据,根据ICMP差错报文的类型,进一步分析错误的原因
- 2: begin checksum[15:8] <= I_icmp_pkg_data; cnt <= cnt + 1'b1;end //ICMP报文首部-校验和:16位校验和的计算方法与IP首部校验和计算方法一致,该校验和需要对ICMP首部和ICMP数据做校验
- 3: begin checksum[7:0] <= I_icmp_pkg_data; cnt <= cnt + 1'b1;end //ICMP报文首部-校验和:16位校验和的计算方法与IP首部校验和计算方法一致,该校验和需要对ICMP首部和ICMP数据做校验
- 4: begin O_icmp_req_id[15:8] <= I_icmp_pkg_data; cnt <= cnt + 1'b1;end //ICMP报文首部-标识符:16位标识符对每一个发送的数据报进行标识
- 5: begin O_icmp_req_id[7:0] <= I_icmp_pkg_data; cnt <= cnt + 1'b1;end //ICMP报文首部-标识符:16位标识符对每一个发送的数据报进行标识
- 6: begin O_icmp_req_sq_num[15:8] <= I_icmp_pkg_data; cnt <= cnt + 1'b1;end //ICMP报文首部-序列号:16位对发送的每一个数据报文进行编号
- 7: begin O_icmp_req_sq_num[7:0] <= I_icmp_pkg_data; cnt <= cnt + 1'b1;end //ICMP报文首部-序列号:16位对发送的每一个数据报文进行编号
- 8: begin
- if(type1 == PING_REQUEST && code == 8'h00) begin //如果是远端主机发的ping请求包,那么本地主机需要返回一个ping应答包
- O_icmp_ping_echo_data_valid <= 1'b1; //ping应答有效
- O_icmp_ping_echo_data <= I_icmp_pkg_data;
- end
- else begin
- O_icmp_ping_echo_data_valid <= 1'b0;
- O_icmp_ping_echo_data <= 8'd0;
- end
- cnt <= 4'd0;
- STATE <= WAIT_PACKET_END;
- end
- default: STATE <= RECORD_ICMP_HEADER;
- endcase
- else
- STATE <= RECORD_ICMP_HEADER;
- end
- WAIT_PACKET_END:begin
- if(I_icmp_pkg_valid) begin //继续接收ICMP 报文
- if(O_icmp_ping_echo_data_valid) //ping应答有效
- echo_data_cnt <= echo_data_cnt + 1'b1;//ICMP包计数器
- else
- echo_data_cnt <= 10'd0;
- O_icmp_ping_echo_data_valid <= O_icmp_ping_echo_data_valid;
- O_icmp_ping_echo_data <= I_icmp_pkg_data;
- STATE <= WAIT_PACKET_END;
- end
- else begin
- if(O_icmp_ping_echo_data_valid) begin
- O_icmp_req_en <= 1'b1; //通知ip_tx模块接收到ICMP报文包ping请求,并且发送一个echo ping应答
- O_icmp_req_checksum <= checksum_temp; //输出校验和
- end
- else begin
- O_icmp_req_en <= 1'b0;
- O_icmp_req_checksum <= 16'd0;
- end
- echo_data_cnt <= echo_data_cnt;
- O_icmp_ping_echo_data_valid <= 1'b0;
- O_icmp_ping_echo_data <= 8'd0;
- STATE <= RECORD_ICMP_HEADER;
- end
- end
- endcase
- end
- end
复制代码
3.6.2 ICMP发送模块
该模块收到icmp_pkg_ctrl模块发送的ping应答包使能信号,寄存接收到的信息包括标识符、序列号、校验和、地址和数据长度,通过计数器添加报文头部,再将附加数据从FIFO中读出,组成ping应答包发送到ip_tx模块。
- always@(posedge I_clk or posedge I_reset) begin
- if(I_reset) begin
- cnt1 <= 4'd0;
- cnt2 <= 10'd0;
- request_id <= 16'd0;
- request_sq_num <= 16'd0;
- request_ip_taddress <= 32'd0;
- checksum <= 16'd0;
- echo_data_length <= 10'd0;
- O_icmp_pkg_req <= 1'b0;
- O_icmp_pkg_valid <= 1'b0;
- O_icmp_pkg_data <= 8'd0;
- O_icmp_ping_echo_ren <= 1'b0;
- STATE <= WAIT_ICMP_PACKET;
- end
- else begin
- case(STATE)
- WAIT_ICMP_PACKET:begin
- if(I_icmp_req_en) begin//当接收到ICMP echo ping包,先保存该包的基本信息到寄存器
- request_id <= I_icmp_req_id; //ICMP包的标识符
- request_sq_num <= I_icmp_req_sq_num; //ICMP包的序列号
- request_ip_taddress <= I_icmp_req_ip_addr; //ICMP包的地址
- checksum <= I_icmp_req_checksum; //ICMP包的校验和
- echo_data_length <= I_icmp_ping_echo_data_len; //ICMP包的长度
- O_icmp_pkg_req <= 1'b1; //请求ip_tx模块发送部分,发送ICMP报文
- STATE <= WAIT_PACKET_SEND; //发送ICMP包状态
- end
- else begin
- request_id <= 16'd0;
- request_sq_num <= 16'd0;
- request_ip_taddress <= 32'd0;
- checksum <= 16'd0;
- echo_data_length <= 10'd0;
- O_icmp_pkg_req <= 1'b0;
- STATE <= WAIT_ICMP_PACKET;
- end
- end
- WAIT_PACKET_SEND:begin
- if(I_icmp_pkg_busy) begin//该信号来自ip_tx模块,当有效代表ip_tx模块已经开始准备发送ICMP包,这里需要对ip_tx代码部分时序逻辑确保数据正确给到ip_tx模块
- O_icmp_pkg_req <= 1'b0;
- O_icmp_pkg_valid <= 1'b1;
- O_icmp_pkg_data <= PING_REPLY_TYPE;//回显应答(ping应答)的类型
- STATE <= SEND_PACKET;
- end
- else begin
- O_icmp_pkg_req <= 1'b1;
- O_icmp_pkg_valid <= 1'b0;
- O_icmp_pkg_data <= 8'd0;
- STATE <= WAIT_PACKET_SEND;
- end
- end
- SEND_PACKET:begin
- case(cnt1)
- 0 :begin O_icmp_pkg_data <= 8'h00; cnt1 <= cnt1 + 1'b1;end//回显应答(ping应答)的代码
- 1 :begin O_icmp_pkg_data <= checksum[15:8]; cnt1 <= cnt1 + 1'b1;end//ICMP报文包校验和,直接获取远程主机发送的Ping包校验和
- 2 :begin O_icmp_pkg_data <= checksum[7:0]; cnt1 <= cnt1 + 1'b1;end//ICMP报文包校验和,直接获取远程主机发送的Ping包校验和
- 3 :begin O_icmp_pkg_data <= request_id[15:8]; cnt1 <= cnt1 + 1'b1;end//ICMP报文标识符,直接获取远程主机发送的Ping包标识符
- 4 :begin O_icmp_pkg_data <= request_id[7:0]; cnt1 <= cnt1 + 1'b1;end//ICMP报文标识符,直接获取远程主机发送的Ping包标识符
- 5 :begin O_icmp_pkg_data <= request_sq_num[15:8]; cnt1 <= cnt1 + 1'b1;end//ICMP报文编码,直接获取远程主机发送的Ping序列号
- 6 :begin //从echo FIFO中读取ICMP echo报文的数据部分
- O_icmp_pkg_data <= request_sq_num[7:0];
- cnt1 <= cnt1 + 1'b1;
- O_icmp_ping_echo_ren <= 1'b1;
- end
- 7 :begin//ICMP报文包的数据有效部分
- O_icmp_pkg_valid <= 1'b1;
- O_icmp_pkg_data <= I_icmp_ping_echo_data;
- if(cnt2 == (echo_data_length - 1)) begin
- cnt2 <= 10'd0;
- O_icmp_ping_echo_ren <= 1'b0;
- cnt1 <= cnt1 + 1'b1;
- end
- else begin
- cnt2 <= cnt2 + 1'b1;
- O_icmp_ping_echo_ren <= 1'b1;
- cnt1 <= cnt1;
- end
- end
- 8 :begin
- cnt1 <= 4'd0;
- O_icmp_pkg_data <= 8'd0;
- O_icmp_pkg_valid <= 1'b0;
- STATE <= WAIT_ICMP_PACKET;
- end
- default:;
- endcase
- end
- endcase
- end
- end
复制代码
4 仿真验证仿真代码的顶层如下:
- `timescale 1ns / 1ps
- module sim_top;
- reg I_reset;
- reg I_clk;
- wire b_r_udp_valid;
- wire [7 :0] b_r_udp_data;
- wire [15:0] b_r_udp_data_len;
- wire [15:0] b_r_udp_src_port;
- wire O_a_ip_rx_error;
- wire O_a_mac_rx_error;
- wire O_b_ip_rx_error;
- wire O_b_mac_rx_error;
- test_udp_loop test_udp_loop_u(
- .I_reset (I_reset),
- .I_clk (I_clk),
-
- .b_r_udp_valid (b_r_udp_valid),
- .b_r_udp_data (b_r_udp_data),
- .b_r_udp_data_len (b_r_udp_data_len),
- .b_r_udp_src_port (b_r_udp_src_port),
-
- .O_a_ip_rx_error (O_a_ip_rx_error),
- .O_a_mac_rx_error (O_a_mac_rx_error),
- .O_b_ip_rx_error (O_b_ip_rx_error),
- .O_b_mac_rx_error (O_b_mac_rx_error)
- );
- initial begin
- I_clk = 0;
- I_reset = 1;
- #2000;
- I_reset = 0;
- end
- always #4 I_clk <= ~I_clk;
- endmodule
复制代码
4.1 数据通信仿真例化两个UDP协议栈模块,分别为主机A和主机B,主机A将用户端输入的数据打包,通过GMII接口发送给主机B,主机B将数据解包后输出至用户端,观察各层的信号变化。
4.1.1 仿真代码编写初始状态机为WAIT_UDP_RDY,等待主机A的O_W_udp_busy信号为低时,用户端将I_W_udp_req信号拉高,状态机进入WAIT_UDP_ACK状态。待主机A将O_W_udp_busy拉高时,用户端将I_W_udp_req拉低,状态机跳转至SEND_DATA状态,计数器开始计数,发送的数据为计数器的低8位。当数据都发送完毕时,状态机跳回WAIT_UDP_RDY状态,等待下一次数据的发送。仿真代码如下:
- `timescale 1ns / 1ps
-
- module test_udp_loop(
- input wire I_reset,
- input wire I_clk,
-
- output wire b_r_udp_valid,
- output wire [7 :0] b_r_udp_data,
- output wire [15:0] b_r_udp_data_len,
- output wire [15:0] b_r_udp_src_port,
-
- output wire O_a_ip_rx_error,
- output wire O_a_mac_rx_error,
- output wire O_b_ip_rx_error,
- output wire O_b_mac_rx_error
- );
-
-
- wire a_w_udp_rdy;
- reg a_w_udp_req;
- reg a_w_udp_valid;
- wire [7:0] a_w_udp_data;
- reg [15:0] a_w_udp_len;
- reg a_w_udp_data_read;
-
- wire b_w_udp_rdy;
- reg b_w_udp_req;
- reg b_w_udp_valid;
- wire [7:0] b_w_udp_data;
- reg [15:0] b_w_udp_len;
- reg b_w_udp_data_read;
-
- wire a_r_udp_valid;
- wire [7 :0] a_r_udp_data;
- wire [15:0] a_r_udp_data_len;
- wire [15:0] a_r_udp_src_port;
-
- reg [9 :0] test_data;
- reg [1 :0] STATE;
-
- parameter WAIT_UDP_READY = 0;
- parameter WAIT_UDP_ACK = 1;
- parameter SEND_DATA = 2;
-
- always@(posedge I_clk or posedge I_reset)begin
- if(I_reset) begin
- a_w_udp_req <= 1'b0;
- a_w_udp_valid <= 1'b0;
- a_w_udp_len <= 16'd0;
- test_data <= 10'd0;
- STATE <= WAIT_UDP_READY;
-
- end
- else begin
- case(STATE)
- WAIT_UDP_READY:begin
- if(~a_w_udp_rdy) begin
- a_w_udp_req <= 1'b1;
- STATE <= WAIT_UDP_ACK;
- end
- else begin
- a_w_udp_req <= 1'b0;
- STATE <= WAIT_UDP_READY;
- end
- end
- WAIT_UDP_ACK:begin
- if(a_w_udp_rdy) begin
- a_w_udp_len <= 16'd768;
- a_w_udp_valid <= 1'b1;
- a_w_udp_req <= 1'b0;
- STATE <= SEND_DATA;
- end
- else
- STATE <= WAIT_UDP_ACK;
- end
- SEND_DATA:begin
- if(test_data == 10'd767) begin
- a_w_udp_valid <= 1'b0;
- a_w_udp_len <= 16'd0;
- test_data <= 0;
- STATE <= WAIT_UDP_READY;
- end
- else begin
- a_w_udp_valid <= 1'b1;
- test_data <= test_data + 1'b1;
- STATE <= SEND_DATA;
- end
- end
- endcase
- end
- end
-
- //以下实现A发送,B接收的仿真测试
- wire [7:0] a_gmii_tdata;
- wire a_gmii_tvalid;
- wire [7:0] b_gmii_tdata;
- wire b_gmii_tvalid;
-
- assign a_w_udp_data = test_data[7:0];
-
- udp_stack #
- (
- .CRC_GEN_EN (1'b1),
- .INTER_FRAME_GAP (4'd12)
- )
- A_udp_stack
- (
- .I_uclk (I_clk),
- .I_reset (I_reset),
-
- .I_mac_local_addr (48'h0123456789a2),//本地MAC地址
- .I_udp_local_port (16'd6002 ), //本地端口号
- .I_ip_local_addr (32'hc0a88902 ), //本地ip地址
-
- .I_udp_dest_port (16'd6001 ), //目的端口
- .I_ip_dest_addr (32'hc0a88901 ), //目的IP地址
-
- .O_W_udp_busy (a_w_udp_rdy),
- .I_W_udp_req (a_w_udp_req),
- .I_W_udp_valid (a_w_udp_valid),
- .I_W_udp_data (a_w_udp_data),
- .I_W_udp_len (a_w_udp_len),
-
- .O_R_udp_valid (),
- .O_R_udp_data (),
- .O_R_udp_len (),
- .O_R_udp_src_port (),
-
- .I_gmii_rclk (I_clk),
- .I_gmii_rvalid (b_gmii_tvalid),
- .I_gmii_rdata (b_gmii_tdata),
-
- .I_gmii_tclk (I_clk),
- .O_gmii_tvalid (a_gmii_tvalid),
- .O_gmii_tdata (a_gmii_tdata),
- .O_ip_rerror (O_a_ip_rx_error),
- .O_mac_rerror (O_a_mac_rx_error)
- );
-
- udp_stack #
- (
- .CRC_GEN_EN (1'b1),
- .INTER_FRAME_GAP (4'd12)
- )
- B_udp_stack
- (
- .I_uclk (I_clk),
- .I_reset (I_reset),
-
- .I_mac_local_addr (48'h0123456789a1),//本地MAC地址
- .I_udp_local_port (16'd6001 ), //本地端口号
- .I_ip_local_addr (32'hc0a88901 ), //本地ip地址
-
- .I_udp_dest_port (16'd6002 ), //目的端口
- .I_ip_dest_addr (32'hc0a88902 ), //目的IP地址
-
- .O_W_udp_busy (),
- .I_W_udp_req (0),
- .I_W_udp_valid (0),
- .I_W_udp_data (0),
- .I_W_udp_len (0),
-
- .O_R_udp_valid (b_r_udp_valid),
- .O_R_udp_data (b_r_udp_data),
- .O_R_udp_len (b_r_udp_data_len),
- .O_R_udp_src_port (b_r_udp_src_port),
-
- .I_gmii_rclk (I_clk),
- .I_gmii_rvalid (a_gmii_tvalid),
- .I_gmii_rdata (a_gmii_tdata),
-
- .I_gmii_tclk (I_clk),
- .O_gmii_tvalid (b_gmii_tvalid),
- .O_gmii_tdata (b_gmii_tdata),
- .O_ip_rerror (O_b_ip_rx_error),
- .O_mac_rerror (O_b_mac_rx_error)
- );
-
- endmodule
复制代码
4.1.2 ARP请求仿真结果主机A将数据打包发送给主机B时,由于主机A的cache中查询不到主机B的MAC地址,主机A会先发送一个ARP请求包给主机B。
当数据帧发送至ip_arp_tx模块时,该模块向ARP层的cache发送一个MAC地址查询请求I_mac_cache_ren和需要查询的IP地址I_mac_cache_rip_addr,查询结束后O_mac_cache_rdone拉高,返回的MAC地址为48’h0,说明未查询到MAC地址,ip_arp_tx模块将I_arp_treq_en拉高,准备发送ARP广播。arp_tx模块接收到I_arp_treq_en高电平时,发送ARP请求O_arp_req,ip_arp_tx模块将I_arp_busy拉高,表示握手成功,arp_tx模块开始组ARP广播包。下图为GMII接口发送ARP广播仿真波形图。
最后信号经过MAC层组帧后通过GMII接口发送至主机B。
4.1.3 ARP应答仿真结果
主机B接收到ARP请求包后,会将ARP包中的MAC地址解析,并将自己的MAC地址通过ARP应答包发送给主机A。主机A收到ARP应答包后,将主机B的MAC地址存入cache中。下图为解包后发送至arp_rx模块的数据。
MAC层解析数据的类型为ARP包,将该包数据通过ip_arp_rx模块发送给arp_rx模块,解析出IP地址、MAC地址,并将该包识别为ARP请求包。arp_rx发送arp_req_valid,信号给arp_tx模块,请求发送ARP应答包,同时将ARP请求包中的主机A的MAC地址写入cache中。arp_tx模块接收到应答请求并寄存,向ip_arp_tx模块发送请求,ip_arp_tx模块将busy信号拉高以表示握手成功。busy信号为高后,arp_tx模块组ARP应答包,将本地MAC地址等信息发送至下层协议。
ARP层发出的应答数据包经MAC层组帧,通过GMII接口发送至主机A。下图为主机A GMII接口接收到的ARP请求包数据。
主机A的MAC层识别数据为ARP数据包类型,发送至arp_rx模块中,arp_rx模块解析对方发送的IP地址和MAC地址,将MAC地址存入cache中。至此,地址解析完成,两机之间可以完成正常数据通信。
4.1.4 UDP发送仿真结果
udp_tx模块与下层模块握手成功后,将数据通过移位寄存器延迟8个周期,组成UDP包,发送至ip_tx模块,组成IP数据包,然后数据经ip_arp_tx模块仲裁,发送至mac_tx模块,组成MAC包后通过GMII接口发送。
下图为UDP层组UDP包的仿真波形图
下图为IP层组IP包的仿真波形图
由于数据都写入了data_fifo,当数据有效数据全部写入完成后,才会将数据读出,所以图中组的MAC包是上一个数据包正在发送的数据包IP头部的标识为16’h0000,而从上层传入的IP数据包包头标识为16’h0001。
下图为MAC层组MAC包的仿真波形图
4.1.5 UDP接收仿真结果主机B的GMII接口接收到主机A发送的数据包,会将数据先存入FIFO做跨时钟域,当数据全部接收完成后,才会将数据读出,图中正在接收的数据包IP头部标识为16’h0003,发送至上层协议的数据包标识为16’h0002,即发送至IP层的数据包为上一个帧。下图为过滤MAC头部的仿真波形图
通过计数器去掉IP头部和UDP头部,最终得到有效数据,和发送的数据一致,如图所示。
4.2 PAUSE流控仿真发送数据的内容和速率与上一节的仿真中一致,每隔一定的时间,在用户端组一个PAUSE帧发送至主机A,暂停时间设置为16’h007F,即主机A实际暂停时间为8128个时钟周期(7F(h) << 6 = 8128(d))。
4.2.1 仿真代码编写通过状态机和计数器,控制PAUSE帧发送到主机A的时序和间隔,同时不断地向主机A的用户端写数据。仿真代码如下:
- always@(posedge I_clk or posedge I_reset)begin
- if(I_reset) begin
- pause_data <= 8'd0;
- pause_vld <= 1'b0;
- cnt1 <= 'd0;
- data_cnt <= 'd0;
- STATE2 <= WAIT_PAUSE_RDY;
- end
- else begin
- case(STATE2)
- WAIT_PAUSE_RDY:begin
- data_cnt <= 'd0;
- if(cnt1 == 16'd5000) begin
- cnt1 <= 'd0;
- STATE2 <= SEND_PAUSE_DATA;
- end
- else begin
- cnt1 <= cnt1 + 1'b1;
- STATE2 <= WAIT_PAUSE_RDY;
- end
- end
- SEND_PAUSE_DATA:begin
- data_cnt <= data_cnt + 1'b1;
- case(data_cnt)
- 0 :begin pause_data <= 8'h55; pause_vld <= 1'b1;end
- 1 :begin pause_data <= 8'h55;end
- 2 :begin pause_data <= 8'h55;end
- 3 :begin pause_data <= 8'h55;end
- 4 :begin pause_data <= 8'h55;end
- 5 :begin pause_data <= 8'h55;end
- 6 :begin pause_data <= 8'h55;end
- 7 :begin pause_data <= 8'hd5;end
-
- 8 :begin pause_data <= 8'h01;end
- 9 :begin pause_data <= 8'h80;end
- 10 :begin pause_data <= 8'hc2;end
- 11 :begin pause_data <= 8'h00;end
- 12 :begin pause_data <= 8'h00;end
- 13 :begin pause_data <= 8'h01;end//pause广播地址
- 14 :begin pause_data <= 8'h01;end
- 15 :begin pause_data <= 8'h23;end
- 16 :begin pause_data <= 8'h45;end
- 17 :begin pause_data <= 8'h67;end
- 18 :begin pause_data <= 8'h89;end
- 19 :begin pause_data <= 8'ha1;end
-
- 20 :begin pause_data <= 8'h88;end
- 21 :begin pause_data <= 8'h08;end
-
- 22 :begin pause_data <= 8'h00;end
- 23 :begin pause_data <= 8'h01;end
-
- 24 :begin pause_data <= 8'h00;end
- 25 :begin pause_data <= 8'h7f;end
-
- 26 :begin pause_data <= 8'hff;end
- 27 :begin pause_data <= 8'hff;end
- 36 :begin pause_data <= 8'h15;end
- 37 :begin pause_data <= 8'hbf;end
- 38 :begin pause_data <= 8'h4b;end
- 39 :begin pause_data <= 8'h6c;end
- 40 :begin
- pause_data <= 8'h00;
- pause_vld <= 1'b0;
- STATE2 <= WAIT_PAUSE_RDY;
- end
- default:pause_data <= 8'h00;
- endcase
- end
- default: begin
- pause_data <= 8'd0;
- pause_vld <= 1'b0;
- cnt1 <= 'd0;
- data_cnt <= 'd0;
- STATE2 <= WAIT_PAUSE_RDY;
- end
- endcase
- end
- end
复制代码
4.2.2 PAUSE流控仿真结果主机A接收到PAUSE帧后,会将PAUSE帧里的信息送入mac_tx_frame_ctrl模块中进行解析,得到暂停时间,并发送给至mac_tx模块,如图所示。
当mac_tx模块中的读状态机进入帧间隔状态时,通过计数器将pause_flag拉高一定的时间。检测到下一帧发送方的MAC地址与PAUSE帧发送方的MAC地址相同,均为48’h0123456789a1,进入暂停状态。
仿真波形图如图,由下图可知,pause_flag拉高的时间为65024ns,即为8128个时钟周期。
4.3 ICMP层仿真向主机A发送一个ping请求包,主机A成功接收到ping请求包后,发送一个ping应答包,其中的额外数据与请求包相同。
4.3.1 仿真代码编写组一个ping请求包给主机A,仿真代码与组pause帧类似,其中IP头部的协议类型为8’h01,ICMP头部的type字段为8’h08,code字段为8’h00,表示主动请求。
4.3.2 ICMP回显应答仿真结果如图所示,ip_rx模块接收到ICMP数据报文会将标识符、序列号、校验和等信息拆解出来,并将有效数据存入FIFO。一帧数据接收完成后,将发送ping应答请求信号给ip_tx模块。
请求信号icmp_req_en拉高后,icmp_pkg_tx模块中的icmp_pkg_req信号也拉高,并发送给ip_tx模块,等待ip_tx模块将icmp_pkg_req拉高,表示握手成功,开始发送icmp回显应答数据。ping应答包的type字段为8’h00,code字段为8’h00,表示回显应答,其有效数据和接收到的ping请求包额外数据保持一致。
5 上板调试