问答 店铺
热搜: ZYNQ FPGA discuz

QQ登录

只需一步,快速开始

微信登录

微信扫码,快速开始

微信扫一扫 分享朋友圈

已有 305 人浏览分享

Scan me!
开启左侧

[MILIANPAI-F01-EG4D]FPGA_以太网通信连载-01UDP RGMII千兆以太网测试

[复制链接]
305 0
UT发布  工单组 发表于  6 天前 | 显示全部楼层 |阅读模式
安路-FPGA课程
安路课程: 通信方案 » UDP通信原理以及三速UDP(支持巨帧)协议栈实现详解
安路系列: EG4
本帖最后由 UT发布 于 2025-4-7 14:23 编辑

软件版本:TD_5.6.4_Release_97693
操作系统:WIN10 64bit
硬件平台:适用安路(Anlogic)FPGA
登录米联客FPGA社区-www.uisrc.com视频课程、在线答疑!
1 概述

本文介绍了基于FPGA米联客UDP协议栈的实现原理、内部逻辑、仿真测试、上板验证。该协议栈具有以下功能:

1实现了ARP请求和应答报文的发送,完成IP地址和MAC地址的映射。

2)实现了ICMP回显应答报文的发送。

3)实现了UDP回环通信。

4)实现了PAUSE帧对数据流量的控制。

2 以太网协议
2.1 以太网介绍

以太网是IEEE 802.3标准下的计算机局域网技术,具有传输速率高、结构简单、工作可靠等优点,是当今现有局域网采用的最通用的协议标准。

以太网最早由Xerox公司创建,并由XeroxIntelDEC公司联合开发。之后IEEE 802.3标准定义了以太网的技术标准,包括物理层的连线、电信号和介质访问层协议的内容。随着技术的发展,以太网逐渐标准化,并成为了当前应用最普遍的局域网技术。

以太网是一种传输规则,收发双方必须遵循这些规则才能使数据有效传输。OSIOpen System Interconnection)七层模型和TCP/IP四层模型是常用的数据传输模型,本设计使用OSI七层模型设计以太网传输模块,其各个子层的功能如下表所示。



层级
名称
作用
常用协议
7
应用层(Application Layer
为应用程序或用户请求提供各种服务
文件传输、电子邮件、虚拟终端
HTTPFTPSMTPPOP3NNTP
6
表示层(Presentation Layer
数据编码、格式转换、数据加密
LLPNBSSP
5
会话层(Session Layer
创建、管理和维护会话
建立或解除与其他接点的联系
SSLDAP
4
传输层(Transport Layer
提供端对端的接口,数据通信
TCPUDP
3
网络层(Network Layer
为数据包选择路由,IP地址及路由选择
IPICMP
2
数据链路层(Data Link Layer
提供介质访问和链路管理。传输有地址的帧,错误检测功能
MACARP
1
物理层(Physical Layer
管理通信设备和网络媒体之间的互联互通以二进制数据形式在物理媒体上传输数据
光纤、双绞线(介质)

数据从上至下逐级封装,加入每层的头部信息,在物理层转换为比特流发送;接收端使用逆向顺序把数据逐级解封装,发送给应用层。FPGA的UDP通信只做到了传输层,物理层一般由外部PHY芯片提供,没有应用层、表示层和会话层。

2.2 物理层

以太网物理层是OSI模型中的第一层,它负责数据的编码、解码、数模转换和数据在物理介质上的传输等。

在10Mbps、100Mbps和1Gbps速率的以太网中,通常采用RJ45网口和双绞线作为物理介质传输数据,RJ45连接器是一种8针连接器,提供4对提供信号输出。在10Mbps./100Mbps速率下,使用了其中4根线,在1Gbps速率下使用了全部8根线。

千兆以太网传输的数据由外部PHY芯片提供编解码和数模转换功能。在1000BASE-T标准中采用4D_5PAM编码方式,其带宽相比传统二电平编码相比提高了带宽利用率。千兆以太网的数据链路层和物理层之间一般采用RGMII接口连接,其接口连接示意图如下:


image.jpg

该接口采用双沿采样的方案,相比GMII接口减少了I/O数量,从而减小了PCB的面积和布线难度,降低了成本。RGMII接口的控制信号被多路复用,CTL信号复用了ER和EN信号,上升沿传输EN信号,下降沿传输EN信号与ER信号的异或结果。

万兆以太网的内部逻辑通过XGMII接口与FPGA芯片内部的SERDES连接。XGMII接口的数据位宽为32位,SERDES提供PMA和PCS子层,其结构包括锁相环、串并转换器、预加重电路、均衡器、时钟恢复电路等,万兆网的数据经过SERDES进行64/66B的编码,通过10.3125Gbps的速率发出,经过光模块进行光电转换后,采用光纤作为物理介质进行传输。

本文通过千兆以太网来验证UDP协议栈的功能。

2.3 以太网帧格式

数据在以太网中传输时需逐层封装或拆解,以添加或过滤上各层协议对应的首部。UDP数据帧的结构和封装顺序如图所示,用户数据经过UDP层、IP层、MAC层,分别添加了8字节UDP头部、20字节IP头部、22字节MAC头部和4字节CRC校验结果,再经过PHY层传出完整的一帧数据。

image.jpg

下面介绍各层协议的具体功能及其帧格式。

2.3.1 MAC协议

太网MAC层在网络通信中起着至关重要的作用,它负责数据的封装与拆包、传输控制、错误检测与纠正功能,确保数据在网络中正确传输和有效处理。

MAC数据帧格式如下图2.1所示,其包头包含前导码、SFD、目的MAC地址、源MAC地址、类型/长度,最后4个字节为CRC校验位。

image.jpg

各区域具体功能如下:

前导码(7Byte):用于调整时钟使收发节点的时钟同步,接收端通过识别前导码来确定数据帧的边界,以确保接收端能够准确地提取出完整的数据帧。内容为连续7字节的8h55

帧起始定界符SFD1Byte):用于区分前导段和数据段,确保后续数据的正确解析。内容为8hd5

MAC地址(6Byte):MAC地址由48bit数据组成,它是网卡的物理地址,一般固化在网卡的ROM中。在以太网传输的最底层就是根据MAC地址来发送数据,同一个网络里不能有两个相同的MAC地址。MAC地址的前3个字节是组织唯一标识符,用于标识网络硬件制造商,后3个字节是制造商为网卡分配的序列号。

类型/长度(2Byte):该区域可以用来表示MAC数据包下一层的类型,也可以用来描述MAC数据包数据段的长度。该值小于1536表示长度,大于1536表示类型,IP协议对应的数值为0x0800ARP协议为0x0806

FCS4Byte):FCS(错误检测码)是用于检测数据帧在传输过程中是否发生错误的一个字段,它通过CRC(循环冗余校验)算法计算得出,并附加在数据帧的尾部。发送数据时,会根据数据内容生成简短的校验和,并将其与数据一起发送。接收数据时,将再次生成的校验和并将其与发送的校验和进行比较。如果二者相等,则数据没有损坏。

每一帧传输完成后,都必须等待96bit数据传输的时间,即最小帧间隔(IFG),才可以进行下一次以太网帧的传输。由于以太网每个时钟周期发送8bit数据,所以最小帧间隔为12个时钟周期。

2.3.2 CRC校验

CRC校验的实现原理比较简单。设m(x)为需要校验的数据,g(x)为多项式,先将数据左移K位(Kg(x)的最大项系数),然后做模2除法运算,模二运算的本质是异或运算。

比如,当m(x)101011g(x)X3+X+1时,将101011左移3位得到101011000,与1011进行模2除法,过程和结果如下图所示。计算得到的余数为111,将其附加到m(x)数据后得到101011010,即为新的数据帧,该数据与1011进行模二除法得到的余数为0,说明校验正确。

image.jpg

硬件设计中,可以通过串行或并行的方式来实现CRC的计算。串行方式消耗的资源少,但速度较慢。串行处理的硬件电路实现实际上就是寄存器和异或门电路连接成线性反馈移位寄存器。CRC-32生成的多项式为g(x)=x32+x26+x23+x22+x16+x12+x11+x10+x8+x7+x5+x4+x2+x+1,其硬件实现如下图所示。

image.jpg

c1[31:0]表示移位寄存器的次态,用c[31:0]表示移位寄存器的现态,用d[0:0]表示需要校验的串行数据,得到的布尔表达式如下:

  1. c1[0 ] = d[0] ^ c[31];
  2.     c1[1 ] = d[0] ^ c[0] ^ c[31];
  3.     c1[2 ] = d[0] ^ c[1] ^ c[31];
  4.     c1[3 ] = c[2];
  5.     c1[4 ] = d[0] ^ c[3] ^ c[31];
  6.     c1[5 ] = d[0] ^ c[4] ^ c[31];
  7.     c1[6 ] = c[5];
  8.     c1[7 ] = d[0] ^ c[6] ^ c[31];
  9.     c1[8 ] = d[0] ^ c[7] ^ c[31];
  10.     c1[9 ] = c[8];
  11.     c1[10] = d[0] ^ c[9] ^ c[31];
  12.     c1[11] = d[0] ^ c[10] ^ c[31];
  13.     c1[12] = d[0] ^ c[11] ^ c[31];
  14.     c1[13] = c[12];
  15.     c1[14] = c[13];
  16.     c1[15] = c[14];
  17.     c1[16] = d[0] ^ c[15] ^ c[31];
  18.     c1[17] = c[16];
  19.     c1[18] = c[17];
  20.     c1[19] = c[18];
  21.     c1[20] = c[19];
  22.     c1[21] = c[20];
  23.     c1[22] = d[0] ^ c[21] ^ c[31];
  24.     c1[23] = d[0] ^ c[22] ^ c[31];
  25.     c1[24] = c[23];
  26.     c1[25] = c[24];
  27.     c1[26] = d[0] ^ c[25] ^ c[31];
  28.     c1[27] = c[26];
  29.     c1[28] = c[27];
  30.     c1[29] = c[28];
  31.     c1[30] = c[29];
  32.     c1[31] = c[30];
复制代码

并行方式消耗的资源多,但速度快,适合用于使用千兆以太网或万兆以太网的场景。得到8位并行运算的结果,需要对d[7:0]的每一位做迭代运算,对d[0]计算得到的结果已经列出,对d[1]做同样的运算得到的布尔表达式如下:

  1.    c2[0 ] = d[0] ^ c[30];
  2.     c2[1 ] = d[1] ^ d[0] ^ c[30] ^ c[31];
  3.     c2[2 ] = d[1] ^ d[0] ^ c[0] ^ c[30] ^ c[31];
  4.     c2[3 ] = d[1] ^ c[1] ^ c[31];
  5.     c2[4 ] = d[0] ^ c[2] ^ c[30];
  6.     ... ...
  7.     c2[31] = c[29];
复制代码

经过8次迭代运算即可得到CRC-328位并行运算公式。

2.3.3 PAUSE流量控制帧格式

PAUSE帧是一种用于控制数据流量的控制帧,当对端发来的数据量过大,为了防止缓冲区溢出导致的数据丢失,MAC层控制流量控制子层发出流量控制帧。接收方收到PAUSE帧后,会将其内容送到MAC层中的流量控制模块中进行解析,得到需要暂停发送的时间。在一帧结束后开启暂停,暂停计数结束后,开始发送新一帧的数据。

PAUSE帧的格式如下图所示。

image.jpg

目的MAC6Byte):PAUSE帧的目的为保留的组播地址,其值为固定的48h01_80_c2_00_00_01

TYPE2Byte):固定值为16h8808,表示帧类型为MAC控制帧。

OPCODE2Byte):操作码,值为16h0001,表示MAC控制帧中的PAUSE帧。

TIME2Byte):时间参数,它的值表示以太网以当前速率传输512bit数据的时间,接收方实际暂停的时间为该字段数值左移6位得到的值乘时钟周期。

补充数据:有效信息后补0,补齐MAC帧所需要的最小数据数量。

2.3.4 IP协议
image.jpg

MAC数据经过数据链路层传输到网络层时,前导码、SFDMAC地址、类型/长度以及校验字节均被过滤IP数据包传入了网络层。该数据包也不完全是有效数据,其还包含20字节的IP头部,具体见上图IP头部各区域功能如下:

版本(4bit):定义IP协议版本,IPV44h4

首部长度(4bit):定义IP数据包头部长度,表示具有32位字长数据的数量。最小值为5,最大值为15

服务类型(8bit):用于分配优先级、延迟、吞吐量及可靠性,一般为8h00

总长度(16bit):定义整个IP数据包长度。

标识(8bit):发完一包数据自动加1

标记(3bit):最高位保留为0;中间位是否开启分段,1不开启,0开启;最低位表示是否存在下一个分段,0表示为最后一个分段,1表示还存在下一个分段。一般默认为3b010

分段偏移(13bit):表示分段数据在源数据报中的相对位置。

生存时间(8bit):表示以太网数据包可以中转进过多少个路由器,每进过一个路由器,该值就会减少1,直到该值变成0,丢包该包。WIN系统默认为8h80

协议(8bit):指出IP处理过程完成后,传输层的协议。UDP8d17TCP8d6ICMP8d1

首部校验和(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的传输效率更高,但其提供不可靠传输,有可能丢包。各区域的功能如下:

image.jpg

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数据包的结构如下图:

image.jpg

硬件类型(2Byte):硬件地址的类型,以太网的硬件类型为1

协议类型(2Byte):上层协议类型,IP的协议类型为16h0800

硬件地址长度(1Byte):硬件地址(MAC地址)的长度,以字节为单位,其值固定为8h06

协议地址长度(1Byte):IP地址的长度,以字节为单位,固定值8h04

OPCODE2Byte):操作码,值为16h0001表示ARP请求,值为16h0002表示ARP应答。

MAC地址(6Byte):发送方的MAC地址。

IP地址(4Byte):发送方的IP地址。

目的MAC地址(6Byte):发送ARP请求时为广播地址48hff_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应答都属于查询报文,查询报文的报文格式如下:
image.jpg

类型(1Byte):表示ICMP报文的类型,不同类型代表不同的功能,如:8h08表示回显请求,8h00表示回显应答。

代码(1Byte):和类型字段结合使用,共同标识了ICMP报文的详细类型,回显请求和回显应答是该值为8h00

校验和(2Byte):用于判断数据传输时是否出了差错,与IP首部校验的方式相同。

标识符(2Byte):对数据报进行标识,用于区分不同的请求和应答。序列号(2Byte):每次发送完就加1,和标识符一起用于区分不同的请求和应答。

3 程序设计

前面我们介绍了以太网的基本概念,及涉及的各层协议格式,接下来我们通过设计Verilog程序来实现以太网各个子层的功能。程序整体架构图如下:

image.jpg
3.1 MAC

MAC层一边连接GMII接口,一边连接上层协议传来的数据。该层将上层传来的数据包组帧发送出去,或者将接收到的数据帧解析,将信息和拆解出来的数据包传给上层协议。

3.1.1 MAC接收模块

MAC接收模块主要实现以下几个功能:

1)对接收到的MAC帧进行解析,过滤前导码、帧起始定界符、MAC地址、类型、CRC校验位,将上层数据包提取出来,并缓存数据包类型,传给上层做判断。

2)对每帧数据进行CRC校验,与帧末尾的4字节校验位做比较,判断数据的正确性

3)识别接收到的流控帧,将有效信息发送给子层解析,把解析到的暂停时间和源MAC地址输出至MAC接收模块做进一步处理。

4)通过FIFO完成PHY接收时钟和用户接口时钟之间的时钟域的转换,并将数据包输出至上层。

  1. /*******************************uimac_rx模块*********************
  2. --以下是米联客设计的uimac_rx模块
  3. --本模块主要有以下几个功能
  4. --1. 从外部PHY芯片接收mac帧,解析mac帧首部,进行mac地址过滤和帧类型过滤。
  5. --2. 内部子模块crc32_check对每帧数据进行crc32值的计算,判断数据的正确性。
  6. --3. 识别接收的mac流控帧,子模块mac_frame_ctrl提取流控帧中的暂停时间和源mac地址输出至uimac_tx模块。
  7. --4. mac_rx_data_fifo完成phy接收时钟和用户接口时钟之间的时钟域转换,将数据输出。
  8. *********************************************************************/
  9. `timescale  1ns/1ps
  10. module  uimac_rx
  11. (
  12.     input   wire    [47:0]      I_mac_local_addr    ,   //本地MAC地址
  13.     input   wire                I_crc32_en          ,   //使能CRC校验
  14.     input   wire                I_reset             ,   //系统复位
  15.     //MAC接收数据发送给上层协议
  16.     input   wire                I_mac_rclk          ,   //接收时钟
  17.     output  wire                O_mac_rvalid        ,   //MAC帧数据有效
  18.     output  wire    [7:0]       O_mac_rdata         ,   //MAC有效数据
  19.     output  wire    [15:0]      O_mac_rdata_type    ,   //MAC类型
  20.     output  wire                O_mac_rdata_error   ,
  21.     //发送PAUSE控制到mac_send
  22.     output  wire                O_mac_pause_en      ,
  23.     output  wire    [21:0]      O_mac_pause_time    ,
  24.     output  wire    [47:0]      O_mac_pause_addr    ,
  25.     //从硬件层获取的裸MAC数据
  26.     input   wire                I_gmii_rclk         ,   //rgmii接收时钟
  27.     input   wire                I_gmii_rvalid       ,   //gmii接收数据有效使能信号
  28.     input   wire    [7:0]       I_gmii_rdata            //gmii接收数据
  29. );
  30. wire    [7:0]       mac_rdata;
  31. reg                 mac_rdata_valid;
  32. reg     [15:0]      mac_rdata_type;
  33. reg                 mac_rdata_error;
  34. assign  O_mac_rdata         =   mac_rdata;
  35. assign  O_mac_rvalid        =   mac_rdata_valid;
  36. assign  O_mac_rdata_type    =   mac_rdata_type;
  37. assign  O_mac_rdata_error   =   mac_rdata_error;
  38. reg     [10:0]      mac_rdata_cnt;
  39. reg                 mac_wfifo_en;   //FIFO写入数据有效使能
  40. reg                 mac_rfifo_en;   //FIFO读数据使能
  41. reg                 crc_en;         //crc校验使能
  42. reg     [2:0]       crc_cnt;        //移位计数器
  43. wire    [31:0]      crc_data_out;   //crc校验结果输出
  44. reg     [2:0]       STATE;          //写FIFO状态机
  45. reg     [1:0]       S_RFIFO;        //读FIFO状态机
  46. reg     [47:0]      dst_mac_addr;   //MAC帧解析出的目的MAC地址(接收方的MAC地址)
  47. reg     [47:0]      src_mac_addr;   //MAC帧解析出的源MAC地址(发送方的MAC地址)
  48. reg     [15:0]      mac_frame_type; //上层数据包类型(0x0800 ip;0x0806 arp;0x8808 mac_ctrl)
  49. reg                 mac_pause_en;   //PAUSE帧有效使能
  50. reg     [3:0]       cnt;            //对MAC帧头部的字节数计数
  51. reg     [10:0]      mac_wfifo_data_cnt_info;//写帧信息字节数到信息FIFO
  52. reg                 mac_wfifo_en_info;      //写帧信息FIFO有效使能
  53. reg                 mac_rfifo_en_info;      //读帧信息FIFO有效使能
  54. wire    [26:0]      mac_rfifo_data_info;    //从信息FIFO读出帧信息
  55. reg     [10:0]      mac_rdata_len;          //mac帧长度
  56. wire                mac_rfifo_empty_info;   //信息FIFO读空信号
  57. reg     [7:0]       mac_rdata_r1, mac_rdata_r2, mac_rdata_r3, mac_rdata_r4;//打拍
  58. reg                 mac_rvalid_r1, mac_rvalid_r2, mac_rvalid_r3, mac_rvalid_r4;//打拍
  59. localparam  WAIT_SFD            =   3'd0;
  60. localparam  CHECK_MAC_HEADER    =   3'd1;
  61. localparam  WRITE_FIFO          =   3'd2;
  62. localparam  RECORD_FRAME_LENGTH =   3'd3;
  63. localparam  WAIT_FRAME_END      =   3'd4;//STATE
  64. localparam  WAIT_MAC_FRAME          =   2'd0;
  65. localparam  READ_MAC_FRAME_DATA_LENGTH  =   2'd1;
  66. localparam  READ_MAC_FRAME_DATA     =   2'd2;//S_RFIFO
  67. localparam  ARP_TYPE    =   16'h0806;
  68. localparam  IP_TYPE     =   16'h0800;
  69. localparam  MAC_CONTROL_TYPE    =   16'h8808;
复制代码

由于用户接口时钟和PHY芯片接收时钟不同源,因此需要对输入的数据进行跨时钟域处理,再将数据传至上层。由于需要传输的数据量比较大,且用FIFO做跨时钟域比较简单,所以程序中使用异步FIFO做跨时钟域处理。 uimac_rxuimac_tx发送模块中,都使用了两个FIFO做跨时钟域处理,一个FIFO用来缓存数据,另一个FIFO用来缓存需要传递的信息,在一帧输入接收完成后,将信息写入帧信息FIFO,通过FIFO空标志信号来控制一帧数据读出。

信号打4拍再写入数据FIFO是为了过滤掉4字节CRC校验位。

  1. assign  O_mac_pause_addr    =   src_mac_addr;
  2. always@(posedge I_gmii_rclk) begin
  3.     mac_rdata_r1    <=  I_gmii_rdata;
  4.     mac_rdata_r2    <=  mac_rdata_r1;   
  5.     mac_rdata_r3    <=  mac_rdata_r2;
  6.     mac_rdata_r4    <=  mac_rdata_r3;
  7. end
  8. always@(posedge I_gmii_rclk) begin
  9.     mac_rvalid_r1   <=  I_gmii_rvalid;
  10.     mac_rvalid_r2   <=  mac_rvalid_r1;  
  11.     mac_rvalid_r3   <=  mac_rvalid_r2;
  12.     mac_rvalid_r4   <=  mac_rvalid_r3;
  13. end//打4拍,方便fifo只写入有效数据,而不写入crc校验位
  14. mac_rx_data_fifo mac_rx_data_fifo (
  15.     .rst            (I_reset),
  16.     .wr_clk         (I_gmii_rclk),
  17.     .din            (mac_rdata_r4),
  18.     .wr_en          (mac_wfifo_en & I_gmii_rvalid),//mac_wfifo_en控制只写入有效数据部分,I_gmii_rvalid控制最后的CRC部分不写入
  19.     .rd_clk         (I_mac_rclk),
  20.     .rd_en          (mac_rfifo_en),
  21.     .dout           (mac_rdata),
  22.     .full           (),
  23.     .empty          (),
  24.     .rd_data_count  (),
  25.     .wr_data_count  ()
  26. );
  27. mac_rx_frame_fifo mac_rx_frame_fifo (
  28.     .rst            (I_reset),
  29.     .wr_clk         (I_gmii_rclk),
  30.     .din            ({mac_wfifo_data_cnt_info,mac_frame_type}),
  31.     .wr_en          (mac_wfifo_en_info),
  32.     .rd_clk         (I_mac_rclk),
  33.     .rd_en          (mac_rfifo_en_info),
  34.     .dout           (mac_rfifo_data_info),
  35.     .full           (),
  36.     .empty          (mac_rfifo_empty_info)
  37. );
  38. crc32_check crc32_check
  39. (
  40.     .reset          (I_reset),
  41.     .clk            (I_gmii_rclk),
  42.     .CRC32_en       (crc_en & I_crc32_en),
  43.     .CRC32_init     (~mac_rvalid_r4),
  44.     .data           (mac_rdata_r4),
  45.     .CRC_data       (crc_data_out)
  46. );
  47. //mac帧控制,当接收方来不及处理接收数据,需要进行帧控制,通知发送模块。
  48. uimac_tx_frame_ctrl mac_tx_frame_ctrl
  49. (
  50.     .I_clk              (I_gmii_rclk),
  51.     .I_reset            (I_reset),
  52.     .I_mac_pause_en     (mac_rvalid_r4 & mac_pause_en),
  53.     .I_mac_data         (mac_rdata_r4),
  54.     .O_mac_pause_en     (O_mac_pause_en),
  55.     .O_mac_pause_time   (O_mac_pause_time)
  56. );
复制代码

通过状态机分别对FIFO写入数据和FIFO读出数据的时序进行控制。FIFO写入数据的状态机转换图如图所示。

image.jpg

WAIT_SFD:初始状态为WAIT_SFD,等待接收到帧起始定界符8hd5时,跳转到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状态,等待接收下一帧。

  1. always@(posedge I_gmii_rclk or posedge I_reset) begin
  2.     if(I_reset) begin
  3.         dst_mac_addr        <=  48'd0;
  4.         src_mac_addr        <=  48'd0;
  5.         mac_frame_type      <=  16'd0;
  6.         mac_wfifo_en        <=  1'b0;
  7.         mac_wfifo_en_info   <=  1'b0;
  8.         mac_wfifo_data_cnt_info <=  11'd0;
  9.         cnt                 <=  4'd0;
  10.         crc_en              <=  1'b0;
  11.         crc_cnt             <=  3'd4;
  12.         mac_rdata_error     <=  1'b1;
  13.         mac_pause_en        <=  1'b0;
  14.         STATE               <=  WAIT_SFD;
  15.     end
  16.     else begin
  17.         case(STATE)
  18.             WAIT_SFD:begin
  19.                 if(mac_rvalid_r4 & (mac_rdata_r4 == 8'hd5)) begin//以太网帧开始同步,一个字节为mac字段
  20.                     crc_en  <=  1'b1;//使能crc
  21.                     STATE   <=  CHECK_MAC_HEADER;//进入帧头接收
  22.                 end
  23.                 else
  24.                     STATE   <=  WAIT_SFD;
  25.             end
  26.             CHECK_MAC_HEADER:begin
  27.                 case(cnt)
  28.                     4'd0:begin  dst_mac_addr[47:40]     <=  mac_rdata_r4; cnt   <=  cnt + 1'b1; end
  29.                     4'd1:begin  dst_mac_addr[39:32]     <=  mac_rdata_r4; cnt   <=  cnt + 1'b1; end
  30.                     4'd2:begin  dst_mac_addr[31:24]     <=  mac_rdata_r4; cnt   <=  cnt + 1'b1; end
  31.                     4'd3:begin  dst_mac_addr[23:16]     <=  mac_rdata_r4; cnt   <=  cnt + 1'b1; end
  32.                     4'd4:begin  dst_mac_addr[15: 8]     <=  mac_rdata_r4; cnt   <=  cnt + 1'b1; end
  33.                     4'd5:begin  dst_mac_addr[ 7: 0]     <=  mac_rdata_r4; cnt   <=  cnt + 1'b1; end//目的mac
  34.                     4'd6:begin  src_mac_addr[47:40]     <=  mac_rdata_r4; cnt   <=  cnt + 1'b1; end
  35.                     4'd7:begin  src_mac_addr[39:32]     <=  mac_rdata_r4; cnt   <=  cnt + 1'b1; end
  36.                     4'd8:begin  src_mac_addr[31:24]     <=  mac_rdata_r4; cnt   <=  cnt + 1'b1; end
  37.                     4'd9:begin  src_mac_addr[23:16]     <=  mac_rdata_r4; cnt   <=  cnt + 1'b1; end
  38.                     4'd10:begin src_mac_addr[15: 8]     <=  mac_rdata_r4; cnt   <=  cnt + 1'b1; end
  39.                     4'd11:begin src_mac_addr[ 7: 0]     <=  mac_rdata_r4; cnt   <=  cnt + 1'b1; end
  40.                     4'd12:begin mac_frame_type[15: 8]   <=  mac_rdata_r4; cnt   <=  cnt + 1'b1; end//源mac
  41.                     4'd13:begin
  42.                         mac_frame_type[7:0] <=  mac_rdata_r4;
  43.                         cnt <=  4'd0;
  44.                         if(dst_mac_addr == I_mac_local_addr) begin//判断mac是否一致
  45.                             if({mac_frame_type[15:8], mac_rdata_r4} == IP_TYPE || {mac_frame_type[15:8], mac_rdata_r4} == ARP_TYPE) begin
  46.                                 mac_wfifo_en    <=  1'b1;//写fifo使能,只写入数据有效部分
  47.                                 STATE           <=  WRITE_FIFO;
  48.                             end
  49.                             else begin//需要过滤的帧
  50.                                 mac_wfifo_en    <=  1'b0;//禁止写fifo
  51.                                 STATE           <=  WAIT_FRAME_END;//过滤该帧,等待帧结束
  52.                             end
  53.                         end
  54.                         else if(dst_mac_addr == 48'h01_80_c2_00_00_01) begin//如果目的地址为48'h0180c2000001(固定值),mac控制帧,需要进行PAUSE流控制
  55.                             mac_wfifo_en    <=  1'b0;
  56.                             STATE           <=  WAIT_FRAME_END;
  57.                             if({mac_frame_type[15:8], mac_rdata_r4} == MAC_CONTROL_TYPE)//报文类型字段,需要进行pause流控制
  58.                                 mac_pause_en    <=  1'b1;//mac控制帧有效
  59.                             else
  60.                                 mac_pause_en    <=  1'b0;
  61.                         end
  62.                         else if(dst_mac_addr == 48'hff_ff_ff_ff_ff_ff) begin//对于广播地址,只接收arp包,其余类型的广播包全部过滤
  63.                             if({mac_frame_type[15:8], mac_rdata_r4} == ARP_TYPE) begin
  64.                                 mac_wfifo_en    <=  1'b1;//写帧数据fifo使能,只写入有效数据部分
  65.                                 STATE           <=  WRITE_FIFO;
  66.                             end
  67.                             else begin//需要过滤的帧
  68.                                 mac_wfifo_en    <=  1'b0;
  69.                                 STATE           <=  WAIT_FRAME_END;
  70.                             end
  71.                         end
  72.                         else begin//需要过滤的帧
  73.                             mac_wfifo_en    <=  1'b0;
  74.                             STATE           <=  WAIT_FRAME_END;
  75.                         end
  76.                     end
  77.                 endcase
  78.             end
  79.             WRITE_FIFO:begin//将去除首部后的ip数据包或者arp帧存入mac_rx_frame_fifo中,同时对当前数据包的长度进行统计
  80.                 if(I_gmii_rvalid) begin//写帧信息fifo
  81.                     mac_wfifo_data_cnt_info     <=  mac_wfifo_data_cnt_info + 1'b1;//有效数据计数器
  82.                     STATE                       <=  WRITE_FIFO;
  83.                 end
  84.                 else begin
  85.                     if(crc_cnt == 3'd0) begin//crc校验
  86.                         if(crc_data_out != 32'hc704dd7b)
  87.                             mac_rdata_error <=  1'b1;//校验正确
  88.                         else
  89.                             mac_rdata_error <=  1'b0;//校验错误
  90.                         mac_wfifo_en        <=  1'b0;
  91.                         mac_wfifo_en_info   <=  1'b1;//写帧信息fifo使能
  92.                         crc_en              <=  1'b0;
  93.                         crc_cnt             <=  3'd4;
  94.                         STATE               <=  RECORD_FRAME_LENGTH;//写入帧信息到帧信息fifo
  95.                     end
  96.                     else
  97.                         crc_cnt <=  crc_cnt - 1'b1;//crc计算计数器
  98.                 end
  99.             end
  100.             RECORD_FRAME_LENGTH:begin//写帧信息完成后,回到状态机WAIT_SFD
  101.                 mac_wfifo_en_info       <=  1'b0;
  102.                 mac_wfifo_data_cnt_info <=  11'd0;
  103.                 STATE                   <=  WAIT_SFD;//回到帧探测状态机
  104.             end
  105.             WAIT_FRAME_END:begin//等待帧结束
  106.                 if(mac_rvalid_r4)
  107.                     STATE               <=  WAIT_FRAME_END;
  108.                 else begin
  109.                     crc_en              <=  1'b0;
  110.                     mac_pause_en        <=  1'b0;
  111.                     STATE               <=  WAIT_SFD;
  112.                 end
  113.             end
  114.         endcase
  115.     end
  116. end
复制代码

FIFO读出数据的状态机转换图如图所示。

image.jpg

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状态。

  1. always@(posedge I_mac_rclk or posedge I_reset) begin
  2.     if(I_reset) begin
  3.         mac_rfifo_en_info   <=  1'b0;
  4.         mac_rdata_len       <=  11'd0;
  5.         mac_rdata_cnt       <=  11'd0;
  6.         mac_rfifo_en        <=  1'b0;
  7.         mac_rdata_type      <=  16'd0;
  8.         mac_rdata_valid     <=  1'b0;
  9.         S_RFIFO             <=  WAIT_MAC_FRAME;
  10.     end
  11.     else begin
  12.         case(S_RFIFO)
  13.             WAIT_MAC_FRAME:begin
  14.                 if(!mac_rfifo_empty_info) begin//接收mac帧信息fifo非空
  15.                     mac_rfifo_en_info   <=  1'b1;
  16.                     S_RFIFO             <=  READ_MAC_FRAME_DATA_LENGTH;
  17.                 end
  18.                 else
  19.                     S_RFIFO             <=  WAIT_MAC_FRAME;
  20.             end
  21.             READ_MAC_FRAME_DATA_LENGTH:begin
  22.                 mac_rdata_len       <=  mac_rfifo_data_info[26:16];//mac帧长度
  23.                 mac_rdata_type      <=  mac_rfifo_data_info[15:0];//mac类型
  24.                 mac_rfifo_en_info   <=  1'b0;
  25.                 mac_rfifo_en        <=  1'b1;//读数据fifo
  26.                 mac_rdata_valid     <=  1'b1;//数据有效
  27.                 S_RFIFO             <=  READ_MAC_FRAME_DATA;
  28.             end
  29.             READ_MAC_FRAME_DATA:begin
  30.                 if(mac_rdata_cnt < (mac_rdata_len - 1'b1)) begin//读完一帧数据
  31.                     mac_rdata_cnt   <=  mac_rdata_cnt + 1'b1;
  32.                     S_RFIFO         <=  READ_MAC_FRAME_DATA;
  33.                 end
  34.                 else begin
  35.                     mac_rfifo_en    <=  1'b0;
  36.                     mac_rdata_valid <=  1'b0;
  37.                     mac_rdata_cnt   <=  11'd0;
  38.                     mac_rdata_len   <=  11'd0;
  39.                     mac_rdata_type  <=  16'd0;
  40.                     S_RFIFO         <=  WAIT_MAC_FRAME;
  41.                 end
  42.             end
  43.         endcase
  44.     end
  45. end
复制代码
3.1.2 MAC发送模块

MAC发送模块主要实现以下功能:

1)接收IPARP数据包,添加MAC帧首部,并对长度不足64字节的包进行补0

2)通过CRC校验模块,生成CRC校验值添加在帧的末尾。

3)通过流控模块,接收MAC接收模块发送的暂停信号,进行流量控制。

4)通过FIFO完成PHY发送时钟和用户接口时钟之间的时钟域的转换,并将数据输出至外部PHY芯片。

该模块数据跨时钟域转换的方式和uimac_rx模块类似,使用两个FIFO对数据进行处理。通过DR1_LOGIC_SHIFTER原语将数据延时,将帧头插入,该方法在uiip_txuiudp_tx模块中也有所体现,该原语可在TD安装路径下的arch文件夹的dr1_macro.v文件中找到。

image.jpg

控制FIFO写入数据的状态机跳转图如所示。

image.jpg

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状态,等待接收下一帧数据。

  1. always@(posedge I_mac_tclk or posedge rst) begin
  2.     if(rst) begin
  3.         mac_wfifo_en_info           <=  1'b0;   //MAC消息FIFO,把MAC的信息包括,目的MAC地址、有效数据长度、帧类型写入到info fifo暂存
  4.         mac_wfifo_data_addr_info    <=  48'd0;  //MAC目的地址,暂存info fifo
  5.         mac_wfifo_data_type_info    <=  16'd0;  //MAC帧类型,暂存info fifo
  6.         mac_wfifo_data_cnt_info     <=  11'd0;  //MAC数据部分发送字节计数器
  7.         mac_wfifo_en                <=  1'b0;   //将帧数据写入到mac_tx_data_fifo缓存
  8.         mac_wfifo_data              <=  8'd0;   //将帧数据写入到mac_tx_data_fifo缓存
  9.         O_mac_tbusy                 <=  1'b1;   //通知外部模块,非忙
  10.         S_WFIFO                     <=  WAIT_DATA_PACKET;
  11.     end
  12.     else begin
  13.         case(S_WFIFO)
  14.             WAIT_DATA_PACKET:begin
  15.                 if(mac_wfifo_data_cnt > SEND_PAUSE_THRESHOLD) begin//当FIFO写通道数据计数器大于SEND_PAUSE_THRESHOLD,不进行新的一帧传输,O_mac_tbusy为握手信号,不进行握手(拉高)
  16.                     O_mac_tbusy                 <=  1'b0;
  17.                     S_WFIFO                     <=  WAIT_DATA_PACKET;
  18.                 end
  19.                 else begin
  20.                     if(I_mac_tvalid) begin//当有效数据发送过来后开始接收数据并且缓存到FIFO
  21.                         O_mac_tbusy             <=  1'b1;               //uimac_tx 忙
  22.                         mac_wfifo_en            <=  1'b1;               //将数据写入FIFO
  23.                         mac_wfifo_data          <=  I_mac_tdata;        //写入FIFO的数据
  24.                         mac_wfifo_data_addr_info<=  I_mac_tdest_addr;   //目的MAC地址
  25.                         mac_wfifo_data_type_info<=  {14'd0, I_mac_tdata_type};//数据类型
  26.                         mac_wfifo_data_cnt_info <=  mac_wfifo_data_cnt_info + 1'b1;//一帧数据的长度,以BYTE为单位
  27.                         S_WFIFO                 <=  WRITE_FIFO;//进入下一个状态等待写FIFO
  28.                     end
  29.                     else begin
  30.                         O_mac_tbusy             <=  1'b0;           //uimac_tx 非忙
  31.                         S_WFIFO                 <=  WAIT_DATA_PACKET;
  32.                     end
  33.                 end
  34.             end
  35.             WRITE_FIFO:begin//写数据到FIFO该FIFO用于缓存udp协议发送过来的数据
  36.                 if(I_mac_tvalid) begin//一帧数据接收过程中O_gmii_tdata_valid始终为高电平
  37.                     mac_wfifo_en                <=  1'b1;//继续写FIFO
  38.                     mac_wfifo_data              <=  I_mac_tdata;//写入FIFO的数据
  39.                     mac_wfifo_data_cnt_info     <=  mac_wfifo_data_cnt_info + 1'b1;//帧字节计数器累加
  40.                     S_WFIFO                     <=  WRITE_FIFO;
  41.                 end
  42.                 else begin
  43.                     if(mac_wfifo_data_cnt_info < 11'd46) begin//当一包/帧数据的长度小于46字节,自动补0(一帧数据最小64bytes,其中数据部分最小46bytes)
  44.                         mac_wfifo_en            <=  1'b1;
  45.                         mac_wfifo_data          <=  8'd0;
  46.                         mac_wfifo_en_info       <=  1'b0;
  47.                         mac_wfifo_data_cnt_info <=  mac_wfifo_data_cnt_info + 1'b1;
  48.                         S_WFIFO                 <=  WRITE_FIFO;
  49.                     end
  50.                     else begin//当一包/帧数据接收完,写包/帧信息 到包/帧信息FIFO
  51.                         mac_wfifo_en            <=  1'b0;
  52.                         mac_wfifo_data          <=  8'd0;
  53.                         mac_wfifo_en_info       <=  1'b1;
  54.                         S_WFIFO                 <=  RECORD_DATA_PACKET_INFO;
  55.                     end
  56.                 end
  57.             end
  58.             RECORD_DATA_PACKET_INFO:begin//时序中,该周期完成写包/帧信息 到包/帧信息FIFO
  59.                 mac_wfifo_en_info               <=  1'b0;
  60.                 mac_wfifo_data_addr_info        <=  48'd0;
  61.                 mac_wfifo_data_type_info        <=  16'd0;
  62.                 mac_wfifo_data_cnt_info         <=  11'd0;
  63.                 S_WFIFO                         <=  WAIT_DATA_PACKET;
  64.             end
  65.         endcase
  66.     end
  67. end
复制代码
控制FIFO读出数据的状态机转换图如图所示。
image.jpg

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校验数据。

  1. //完成MAC帧的发送,用到了前面的帧缓存FIFO,信息缓存FIFO,以及SHIFT寄存器(实现MAC帧头信息插入)
  2. always@(posedge I_gmii_tclk or posedge I_reset) begin
  3.     if(I_reset) begin
  4.         mac_rfifo_data_en           <=  1'b0;   
  5.         mac_rfifo_en_info           <=  1'b0;   
  6.         mac_rfifo_data_cnt          <=  11'd0;  
  7.         mac_rfifo_data_length       <=  11'd0;  
  8.         mac_rfifo_data_addr         <=  48'd0;  
  9.         ether_type                  <=  16'd0;  
  10.         inter_gap_cnt               <=  4'd0;   
  11.         S_RFIFO                     <=  WAIT_DATA_PACKET;
  12.     end
  13.     else begin
  14.         case(S_RFIFO)
  15.             WAIT_DATA_PACKET:begin
  16.                 if(!mac_rfifo_empty_info) begin//帧信息FIFO非空代表有帧需要发送
  17.                     mac_rfifo_en_info       <=  1'b1;//FIFO是设置的FWT模式,如果只有1帧数据,那么FIFO被读空,否则FIFO输出更新到下一帧
  18.                     S_RFIFO                 <=  READ_DATA_PACKET_INFO;
  19.                 end
  20.                 else
  21.                     S_RFIFO                 <=  WAIT_DATA_PACKET;
  22.             end
  23.             READ_DATA_PACKET_INFO:begin
  24.                 if(mac_rfifo_data_info[15:0] == 16'h0002) begin//发送的ARP包类型为应答包
  25.                     ether_type              <=  ARP_PACKET;
  26.                     mac_rfifo_data_addr     <=  mac_rfifo_data_info[74:27];//MAC地址
  27.                 end
  28.                 else if(mac_rfifo_data_info[15:0] == 16'h0003) begin
  29.                     ether_type              <=  ARP_PACKET;
  30.                     mac_rfifo_data_addr     <=  48'hff_ff_ff_ff_ff_ff;//广播地址
  31.                 end
  32.                 else begin
  33.                     ether_type              <=  IP_PACKET;//IP 包
  34.                     mac_rfifo_data_addr     <=  mac_rfifo_data_info[74:27];//MAC地址
  35.                 end
  36.                 mac_rfifo_data_length       <=  mac_rfifo_data_info[26:16];//数据长度
  37.                 mac_rfifo_en_info           <=  1'b0;
  38.                 if(pause_flag && mac_rfifo_data_info[74:27] == pause_dst_mac_addr) begin//如果存在PAUSE帧需要发送,并且目的地址和当前目的地址一致
  39.                     mac_rfifo_data_en       <=  1'b0;//PAUSE 帧阶段不从FIFO读数据   
  40.                     S_RFIFO                 <=  WAIT_PAUSE_END;//等待PAUSE流控制结束
  41.                 end
  42.                 else begin
  43.                     mac_rfifo_data_en       <=  1'b1;
  44.                     S_RFIFO                 <=  READ_DATA_PACKET;
  45.                 end
  46.             end
  47.             READ_DATA_PACKET:begin
  48.                 if(mac_rfifo_data_cnt == (mac_rfifo_data_length - 1'b1)) begin//一帧数据从FIFO读完(上一个状态已经读出了一个周期,所以这里少计一个数)
  49.                     mac_rfifo_data_en       <=  1'b0;
  50.                     mac_rfifo_data_length   <=  11'd0;
  51.                     mac_rfifo_data_cnt      <=  11'd0;
  52.                     mac_rfifo_data_addr     <=  48'd0;
  53.                     ether_type              <=  16'd0;
  54.                     S_RFIFO                 <=  WAIT_CRC_TRANS_DONE;   
  55.                 end
  56.                 else begin
  57.                     mac_rfifo_data_en       <=  1'b1;
  58.                     mac_rfifo_data_cnt      <=  mac_rfifo_data_cnt + 1'b1;
  59.                     S_RFIFO                 <=  READ_DATA_PACKET;
  60.                 end
  61.             end
  62.             WAIT_CRC_TRANS_DONE:begin//等待正在发送的MAC数据包CRC发送完成
  63.                 if(crc_cnt)
  64.                     S_RFIFO                 <=  WAIT_CRC_TRANS_DONE;
  65.                 else
  66.                     S_RFIFO                 <=  ADD_IFG;
  67.             end
  68.             ADD_IFG:begin//数据包发送后,插入帧间隔,2帧之间最少需要IFGmini=96bit/speed,比如1000M 96ns 100M 960ns 10M 9600ns
  69.                 if(inter_gap_cnt == (IFG - 4'd4)) begin//插入最小帧间隔周期,在此状态机,MAC_SEND_FLOW_CONTROL 流控制模可以发送PAUSE帧,减去4'd4是本计数器结束后,距离下一帧发送实际需要还要经过4个时钟周期
  70.                     inter_gap_cnt           <=  4'd0;
  71.                     S_RFIFO                 <=  WAIT_DATA_PACKET;//进入WAIT_DATA_PACKET
  72.                 end
  73.                 else begin
  74.                     inter_gap_cnt           <=  inter_gap_cnt + 1'b1;
  75.                     S_RFIFO                 <=  ADD_IFG;
  76.                 end
  77.             end
  78.             WAIT_PAUSE_END:begin//等待暂停结束后重新传输数据
  79.                 if(pause_flag) begin//pause 控制
  80.                     mac_rfifo_data_en       <=  1'b0;
  81.                     S_RFIFO                 <=  WAIT_PAUSE_END;
  82.                 end
  83.                 else begin
  84.                     mac_rfifo_data_en       <=  1'b1;//暂停结束后,继续读帧FIFO中数据
  85.                     S_RFIFO                 <=  READ_DATA_PACKET;
  86.                 end
  87.             end
  88.         endcase
  89.     end
  90. end
  91. always@(posedge I_gmii_tclk or posedge I_reset) begin
  92.     if(I_reset) begin
  93.         O_gmii_tvalid       <=  1'b0;
  94.         mac_tdata           <=  8'd0;
  95.         mac_tdata_crc_en    <=  1'b0;
  96.         data22_cnt          <=  5'd0;
  97.         data22_shift_cnt    <=  5'd22;
  98.         crc_cnt             <=  3'd4;
  99.         crc_read            <=  1'b0;
  100.     end
  101.     else if(mac_rfifo_data_en) begin
  102.         case(data22_cnt)//这个阶段移位寄存器进行数据的填充
  103.             0   :begin  mac_tdata   <=  8'h55;    data22_cnt  <=  data22_cnt + 1'b1; O_gmii_tvalid    <=  1'b1; data22_shift_cnt  <=  5'd22;end
  104.             1   :begin  mac_tdata   <=  8'h55;    data22_cnt  <=  data22_cnt + 1'b1; end
  105.             2   :begin  mac_tdata   <=  8'h55;    data22_cnt  <=  data22_cnt + 1'b1; end  
  106.             3   :begin  mac_tdata   <=  8'h55;    data22_cnt  <=  data22_cnt + 1'b1; end
  107.             4   :begin  mac_tdata   <=  8'h55;    data22_cnt  <=  data22_cnt + 1'b1; end
  108.             5   :begin  mac_tdata   <=  8'h55;    data22_cnt  <=  data22_cnt + 1'b1; end
  109.             6   :begin  mac_tdata   <=  8'h55;    data22_cnt  <=  data22_cnt + 1'b1; end
  110.             7   :begin  mac_tdata   <=  8'hd5;    data22_cnt  <=  data22_cnt + 1'b1; end
  111.             
  112.             8   :begin  mac_tdata   <=  mac_rfifo_data_addr[47:40]; data22_cnt  <=  data22_cnt + 1'b1;
  113. mac_tdata_crc_en <=  1'b1; end
  114.             9   :begin  mac_tdata   <=  mac_rfifo_data_addr[39:32]; data22_cnt  <=  data22_cnt + 1'b1; end
  115.             10  :begin  mac_tdata   <=  mac_rfifo_data_addr[31:24]; data22_cnt  <=  data22_cnt + 1'b1; end
  116.             11  :begin  mac_tdata   <=  mac_rfifo_data_addr[23:16]; data22_cnt  <=  data22_cnt + 1'b1; end
  117.             12  :begin  mac_tdata   <=  mac_rfifo_data_addr[15:8];  data22_cnt  <=  data22_cnt + 1'b1; end
  118.             13  :begin  mac_tdata   <=  mac_rfifo_data_addr[7:0];   data22_cnt  <=  data22_cnt + 1'b1; end
  119.             14  :begin  mac_tdata   <=  I_mac_local_addr[47:40];    data22_cnt  <=  data22_cnt + 1'b1; end
  120.             15  :begin  mac_tdata   <=  I_mac_local_addr[39:32];    data22_cnt  <=  data22_cnt + 1'b1; end
  121.             16  :begin  mac_tdata   <=  I_mac_local_addr[31:24];    data22_cnt  <=  data22_cnt + 1'b1; end
  122.             17  :begin  mac_tdata   <=  I_mac_local_addr[23:16];    data22_cnt  <=  data22_cnt + 1'b1; end
  123.             18  :begin  mac_tdata   <=  I_mac_local_addr[15:8];     data22_cnt  <=  data22_cnt + 1'b1; end
  124.             19  :begin  mac_tdata   <=  I_mac_local_addr[7:0];      data22_cnt  <=  data22_cnt + 1'b1; end
  125.             
  126.             20  :begin  mac_tdata   <=  ether_type[15:8];           data22_cnt  <=  data22_cnt + 1'b1; end
  127.             21  :begin  mac_tdata   <=  ether_type[7:0];            data22_cnt  <=  data22_cnt + 1'b1; end
  128.             22  :begin  mac_tdata   <=  mac_tdata_shift_out; end
  129.             default:    data22_cnt  <=  5'd0;
  130.         endcase
  131.     end
  132.     else if(!mac_rfifo_data_en) begin//tmac_en=1阶段会读取mac_tx_frame_info_fifo中所有的数据写到移位寄存器,当tmac_en=0,移位寄存器剩余22个有效数据需要移除
  133.         if(data22_shift_cnt != 5'd0) begin//将移位寄存器组中的剩余22个数据读出
  134.             mac_tdata           <=  mac_tdata_shift_out;
  135.             data22_shift_cnt    <=  data22_shift_cnt - 1'b1;
  136.         end
  137.         else begin
  138.             if(I_crc32_en && O_gmii_tvalid) begin //开始传送帧的CRC32校验值
  139.                 O_gmii_tvalid       <=  1'b1;
  140.                 data22_cnt          <=  5'd0;
  141.                 mac_tdata_crc_en    <=  1'b0;//停止CRC计算
  142.                 crc_read            <=  1'b1;//开始传输CRC32校验值
  143.                 if(crc_cnt != 3'd0)
  144.                     crc_cnt         <=  crc_cnt - 1'b1;
  145.                 else begin
  146.                     O_gmii_tvalid   <=  1'b0;
  147.                     crc_read        <=  1'b0;//4字节的CRC校验值传输完毕
  148.                     crc_cnt         <=  3'd4;
  149.                 end
  150.             end
  151.             else begin//不进行CRC32校验,无需传输校验值
  152.                 O_gmii_tvalid       <=  1'b0;
  153.                 data22_shift_cnt    <=  5'd0;
  154.             end
  155.         end
  156.     end
  157. end
复制代码
3.1.3 CRC校验模块

通过Easics CRC Tool网页生成CRC校验代码,将代码稍作修改,得到CRC校验计算模块。uimac_tx将得到的校验和添加到帧末尾,uimac_rx模块将接收到的CRC校验位也进行CRC校验,若校验计算结果为32'hc704dd7b则校验正确该值由CRC32多项式32h1cdf4421经过位反演和字节反转得到在发送和接收CRC校验和时,其值是以低字节序进行传输的,即最高位在末尾,发送时需要注意将CRC校验结果转为低字节序。

  1. `timescale 1ns / 1ps
  2. module crc32_gen(
  3. input   reset,
  4. input   clk,
  5. input   CRC32_en,         //CRC校验使能信号
  6. input   CRC32_init,       //CRC校验值初始化信号
  7. input   CRC_read,
  8.      //input   CRC32_valid,      //CRC校验值维持有效
  9. input  [7:0]  data,  
  10. output [7:0]  CRC_out
  11. );
  12. reg [31:0]   CRC_temp;
  13. assign CRC_out = CRC_read ? ~{CRC_temp[24], CRC_temp[25], CRC_temp[26], CRC_temp[27],
  14.                                CRC_temp[28], CRC_temp[29], CRC_temp[30], CRC_temp[31]} : 8'h00;           
  15.    
  16. always@(posedge clk or posedge reset)         
  17.    if(reset)
  18.         CRC_temp <= 32'hffffffff;         
  19.    else if(CRC32_init)
  20.         CRC_temp <= 32'hffffffff;
  21.    else if(CRC32_en)
  22.       begin
  23. CRC_temp[0]<=CRC_temp[24]^CRC_temp[30]^data[1]^data[7];
  24. CRC_temp[1]<=CRC_temp[25]^CRC_temp[31]^data[0]^data[6]^CRC_temp[24]^CRC_temp[30]^data[1]^data[7];
  25. 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]
  26. ^data[7];
  27. CRC_temp[3]<=CRC_temp[27]^data[4]^CRC_temp[26]^data[5]^CRC_temp[25]^CRC_temp[31]^data[0]^data[6];
  28. 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]
  29. ^data[7];
  30. 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];
  31. 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];
  32. 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];
  33. 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];
  34. 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];
  35. 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];
  36. 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];
  37. 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];
  38. 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];
  39. 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];
  40. 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];
  41. CRC_temp[16]<=CRC_temp[8]^CRC_temp[29]^data[2]^CRC_temp[28]^data[3]^CRC_temp[24]^data[7];
  42. CRC_temp[17]<=CRC_temp[9]^CRC_temp[30]^data[1]^CRC_temp[29]^data[2]^CRC_temp[25]^data[6];
  43. CRC_temp[18]<=CRC_temp[10]^CRC_temp[31]^data[0]^CRC_temp[30]^data[1]^CRC_temp[26]^data[5];
  44. CRC_temp[19]<=CRC_temp[11]^CRC_temp[31]^data[0]^CRC_temp[27]^data[4];
  45. CRC_temp[20]<=CRC_temp[12]^CRC_temp[28]^data[3];
  46. CRC_temp[21]<=CRC_temp[13]^CRC_temp[29]^data[2];
  47. CRC_temp[22]<=CRC_temp[14]^CRC_temp[24]^data[7];
  48. CRC_temp[23]<=CRC_temp[15]^CRC_temp[25]^data[6]^CRC_temp[24]^CRC_temp[30]^data[1]^data[7];
  49. CRC_temp[24]<=CRC_temp[16]^CRC_temp[26]^data[5]^CRC_temp[25]^CRC_temp[31]^data[0]^data[6];
  50. CRC_temp[25]<=CRC_temp[17]^CRC_temp[27]^data[4]^CRC_temp[26]^data[5];
  51. 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];
  52. 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];
  53. CRC_temp[28]<=CRC_temp[20]^CRC_temp[30]^data[1]^CRC_temp[29]^data[2]^CRC_temp[26]^data[5];
  54. CRC_temp[29]<=CRC_temp[21]^CRC_temp[31]^data[0]^CRC_temp[30]^data[1]^CRC_temp[27]^data[4];
  55. CRC_temp[30]<=CRC_temp[22]^CRC_temp[31]^data[0]^CRC_temp[28]^data[3];
  56. CRC_temp[31]<=CRC_temp[23]^CRC_temp[29]^data[2];
  57.       end
  58.     else if(CRC_read)
  59.         CRC_temp <= {CRC_temp[23:0], 8'hff};
  60.          
  61. endmodule
复制代码
3.1.4 PAUSE帧流控制模块

uimac_rx模块接收到PAUSE帧后,会将有效数据段送入mac_tx_frame_ctrl模块进行解析,得到暂停时间和PAUSE帧发送方的MAC地址,将其发送给uimac_tx模块的mac_tx_pause_ctrl子模块。

  1. /*******************************mac_tx_frame_ctrl模块*********************
  2. --以下是米联客设计的mac_tx_frame_ctrl模块,用于产生MAC发送模块的PAUSE暂停发送
  3. 1.
  4. *********************************************************************/
  5. `timescale 1ns/1ps
  6. module  uimac_tx_frame_ctrl
  7. (
  8.     input   wire                I_clk,
  9.     input   wire                I_reset,
  10.     input   wire                I_mac_pause_en,
  11.     input   wire    [7:0]       I_mac_data,
  12.     output  reg                 O_mac_pause_en,
  13.     output  reg     [21:0]      O_mac_pause_time//发送MAC停止发送数据的时间
  14. );
  15. reg     [15:0]              opcode;
  16. reg     [15:0]              pause_time;//pause_time字段为发送MAC停止发送数据的时间,每单位为512bit传输时间,比如数值为16’d1024表示暂停时间为MAC传输1024*512bit数据所需要的时间
  17. reg     [2:0]               cnt;
  18. reg                         STATE;
  19. localparam  READ_FRAME      =   0;
  20. localparam  WAIT_FRAME_END  =   1;
  21. localparam  PAUSE_FRAME     =   16'h0001;//操作码,固定值为0x0001
  22. always@(posedge I_clk or posedge I_reset) begin
  23.     if(I_reset) begin
  24.         cnt                 <=  3'd0;
  25.         opcode              <=  16'd0;
  26.         pause_time          <=  16'd0;
  27.         O_mac_pause_en      <=  1'b0;
  28.         O_mac_pause_time    <=  22'd0;
  29.         STATE               <=  READ_FRAME;
  30.     end
  31.     else begin
  32.         case(STATE)
  33.             READ_FRAME:begin
  34.                 if(I_mac_pause_en)//帧流控制有效
  35.                     case(cnt)
  36.                         0:begin opcode[15: 8]   <=  I_mac_data; cnt <=  cnt + 1'b1;end
  37.                         1:begin
  38.                             opcode[ 7: 0]   <=  I_mac_data;
  39.                             if({opcode[15: 8], I_mac_data} == PAUSE_FRAME) begin//判断是PAUSE帧
  40.                                 STATE   <=  READ_FRAME;
  41.                                 cnt     <=  cnt + 1'b1;
  42.                             end
  43.                             else begin
  44.                                 STATE   <=  WAIT_FRAME_END;
  45.                                 cnt     <=  3'd0;
  46.                             end
  47.                         end
  48.                         2:begin pause_time[15: 8]   <=  I_mac_data; cnt <=  cnt + 1'b1;end
  49.                         3:begin pause_time[ 7: 0]   <=  I_mac_data; cnt <=  cnt + 1'b1;end//需要暂停发送的时间
  50.                         4:begin
  51.                             cnt                 <=  3'd0;
  52.                             opcode              <=  16'd0;
  53.                             pause_time          <=  16'd0;
  54.                             O_mac_pause_en      <=  1'b1;//通知MAC发送控制器,接收到了PAUSE帧
  55.                             O_mac_pause_time    <=  {pause_time, 6'd0};//*512/8 = *64 = *(2^6)
  56.                             STATE               <=  WAIT_FRAME_END;//等待帧结束
  57.                         end
  58.                     endcase
  59.                 else
  60.                     STATE   <=  READ_FRAME;
  61.             end
  62.             WAIT_FRAME_END:begin//等待帧结束
  63.                 O_mac_pause_time    <=  22'd0;
  64.                 O_mac_pause_en      <=  1'b0;
  65.                 if(I_mac_pause_en)
  66.                     STATE   <=  WAIT_FRAME_END;
  67.                 else
  68.                     STATE   <=  READ_FRAME;
  69.             end
  70.         endcase
  71.     end
  72. end
  73. endmodule
复制代码

mac_tx_pause_ctrl接收到mac_pasue_en信号为高时,将接收的信息寄存,状态机跳转,等待MAC发送端发送完一帧数据,进入帧间隔等待。当MAC发送模块进入帧间隔状态后,流控模块拉高pause_flag信号,等待暂停时间结束后将信号拉低。

  1. /*******************************mac_tx_pause_ctrl模块*********************
  2. --以下是米联客设计的mac_tx_pause_ctrl MAC发送端,流控制器模块
  3. 1.
  4. *********************************************************************/
  5. `timescale 1ns/1ps
  6. module  uimac_tx_pause_ctrl
  7. (
  8.     input   wire                I_clk,
  9.     input   wire                I_reset,
  10.     input   wire    [2:0]       I_mac_state,
  11.     input   wire                I_mac_pause_en,
  12.     input   wire    [21:0]      I_mac_pause_time,
  13.     input   wire    [47:0]      I_mac_pause_addr,
  14.     output  reg     [47:0]      O_pause_dst_mac_addr,
  15.     output  reg                 O_pause_flag
  16. );
  17. reg     [21:0]          pause_clk_num;
  18. reg     [21:0]          pause_clk_cnt;
  19. reg     [1:0]           STATE;
  20. localparam  WAIT_PAUSE_FRAME        =   2'd0;
  21. localparam  WAIT_CURRENT_SEND_DONE  =   2'd1;
  22. localparam  MAC_SEND_PAUSE          =   2'd2;
  23. localparam  ADD_IFG     =   3'd4;
  24. always@(posedge I_clk or posedge I_reset) begin
  25.     if(I_reset) begin
  26.         pause_clk_num           <=  22'd0;
  27.         pause_clk_cnt           <=  22'd0;
  28.         O_pause_flag            <=  1'b0;
  29.         O_pause_dst_mac_addr    <=  48'd0;
  30.         STATE                   <=  WAIT_PAUSE_FRAME;
  31.     end
  32.     else begin
  33.         case(STATE)
  34.             WAIT_PAUSE_FRAME:begin//等待PAUSE帧
  35.                 O_pause_flag    <=  1'b0;
  36.                 if(I_mac_pause_en) begin    //MAC接收模块接收到PAUSE帧
  37.                     O_pause_dst_mac_addr    <=  I_mac_pause_addr;//MAC发送模块需要发送PAUSE的目的MAC地址        
  38.                     pause_clk_num           <=  I_mac_pause_time;//PAUSE时间,在MAC接收端已经换算好需要PAUSE的时钟周期个数
  39.                     STATE                   <=  WAIT_CURRENT_SEND_DONE;
  40.                 end
  41.                 else begin
  42.                     O_pause_dst_mac_addr    <=  48'd0;
  43.                     pause_clk_num           <=  22'd0;
  44.                     STATE                   <=  WAIT_PAUSE_FRAME;                  
  45.                 end
  46.             end
  47.             WAIT_CURRENT_SEND_DONE:begin//等待当MAC发送状态机在I_mac_state == ADD_IFG状态的时候,设置O_pause_flag标志
  48.                 if(I_mac_state == ADD_IFG) begin
  49.                     O_pause_flag            <=  1'b1;//设置O_pause_flag,通知MAC 帧发送模块,暂停数据发送
  50.                     STATE                   <=  MAC_SEND_PAUSE;
  51.                 end
  52.                 else begin
  53.                     O_pause_flag            <=  1'b0;
  54.                     STATE                   <=  WAIT_CURRENT_SEND_DONE;
  55.                 end
  56.             end
  57.             MAC_SEND_PAUSE:begin//暂停数据发送,等待(pause_clk_num - 3)个时钟周期
  58.                 if(pause_clk_cnt == (pause_clk_num - 3)) begin
  59.                     O_pause_flag            <=  1'b0;
  60.                     O_pause_dst_mac_addr    <=  48'd0;
  61.                     pause_clk_cnt           <=  22'd0;
  62.                     pause_clk_num           <=  22'd0;
  63.                     STATE                   <=  WAIT_PAUSE_FRAME;
  64.                 end
  65.                 else begin
  66.                     O_pause_flag            <=  1'b1;//设置O_pause_flag,通知MAC 帧发送模块,暂停数据发送
  67.                     pause_clk_cnt           <=  pause_clk_cnt + 1'b1;
  68.                     STATE                   <=  MAC_SEND_PAUSE;
  69.                 end
  70.             end
  71.         endcase
  72.     end
  73. end
  74. endmodule
复制代码
3.2 IP_ARP

由于IPARP数据包送至MAC层要经过同一个通道,需要对发送的数据包类型进行判断和仲裁,这就需要额外增加一个IP_ARP层。

3.2.1 IP_ARP接收模块

该模块接收到MAC帧经过MAC层解包得到的数据包,通过类型字段判断该包是IP包还是ARP包,将其送入对应的模块中处理。

  1. /*******************************uiip_arp_rx模块*********************
  2. --以下是米联客设计的uiip_arp_rx模块
  3. 1.该模块1用于区分接收数据是IP包还是ARP包
  4. *********************************************************************/
  5. `timescale 1ns/1ps
  6. module  uiip_arp_rx
  7. (
  8.     input   wire                I_ip_arp_reset,     //复位
  9.     input   wire                I_ip_arp_rclk,      //RX 接收时钟
  10.     output  wire                O_ip_rvalid,        //接收的有效IP信号
  11.     output  wire    [7:0]       O_ip_rdata,         //接收的IP数据
  12.     output  wire                O_arp_rvalid,       //接收的有效ARP信号
  13.     output  wire    [7:0]       O_arp_rdata,        //接收的有效ARP数据
  14.     input   wire                I_mac_rvalid,       //MAC接收到的数据有效信号
  15.     input   wire    [7:0]       I_mac_rdata,        //MAC接收的有效数据
  16.     input   wire    [15:0]      I_mac_rdata_type    //MAC接收到的帧类型
  17. );
  18. reg                 ip_rx_data_valid;   //接收的有效IP信号
  19. reg     [7:0]       ip_rx_data;         //接收的IP数据
  20. reg                 arp_rx_data_valid;  //接收的有效ARP信号
  21. reg     [7:0]       arp_rx_data;        //接收的有效ARP数据
  22. assign  O_ip_rvalid     =   ip_rx_data_valid;
  23. assign  O_ip_rdata      =   ip_rx_data;
  24. assign  O_arp_rvalid    =   arp_rx_data_valid;
  25. assign  O_arp_rdata     =   arp_rx_data;
  26. localparam  ARP_TYPE    =   16'h0806;   //ARP包类型
  27. localparam  IP_TYPE     =   16'h0800;   //IP 包类型
  28. always@(posedge I_ip_arp_rclk or posedge I_ip_arp_reset) begin
  29.     if(I_ip_arp_reset) begin
  30.         ip_rx_data_valid    <=  1'b0;
  31.         ip_rx_data          <=  8'd0;
  32.         arp_rx_data_valid   <=  1'b0;
  33.         arp_rx_data         <=  8'd0;
  34.     end
  35.     else if(I_mac_rvalid) begin
  36.         if(I_mac_rdata_type == IP_TYPE) begin//IP包
  37.             ip_rx_data_valid    <=  1'b1;
  38.             ip_rx_data          <=  I_mac_rdata;
  39.         end
  40.         else if(I_mac_rdata_type == ARP_TYPE) begin//ARP包
  41.             arp_rx_data_valid   <=  1'b1;
  42.             arp_rx_data         <=  I_mac_rdata;
  43.         end
  44.         else begin
  45.             ip_rx_data_valid    <=  1'b0;
  46.             ip_rx_data          <=  8'd0;
  47.             arp_rx_data_valid   <=  1'b0;
  48.             arp_rx_data         <=  8'd0;
  49.         end
  50.     end
  51.     else begin
  52.         ip_rx_data_valid    <=  1'b0;
  53.         ip_rx_data          <=  8'd0;
  54.         arp_rx_data_valid   <=  1'b0;
  55.         arp_rx_data         <=  8'd0;
  56.     end
  57. end
  58. endmodule
复制代码
3.2.2 IP_ARP发送模块

该模块接收IP层和ARP层传来的发送请求,通过busy信号与上层协议模块进行握手,来发送对应的数据。该模块的状态机转换图如图所示。

image.jpg

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_PACKETI_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请求直至收到应答。

  1. always@(posedge I_ip_arp_clk or posedge I_ip_arp_reset)begin
  2.     if(I_ip_arp_reset) begin
  3.         O_mac_cache_ren         <=  1'b0;   //查询MAC cache
  4.         O_mac_cache_rip_addr    <=  32'd0;  //查询MAC cache地址
  5.         O_arp_tbusy             <=  1'b0;   //ip_arp_tx arp 发送准备好   
  6.         O_arp_treq_en           <=  1'b0;   //ip_arp_tx arp请求发送ARP包(当发送IP包,没有找打cache中的MAC的时候发送)
  7.         O_arp_treq_ip_addr      <=  32'd0;  //ARP可以发送模块通过发送带有目的IP地址的ARP请求,获取目的远程主机的MAC地址
  8.         
  9.         O_ip_tbusy              <=  1'b0;   //ip_arp_tx可以发送IP包
  10.         O_mac_tdata_type        <=  2'd0;   //MAC发送数据类型
  11.         O_mac_tvalid            <=  1'b0;   //MAC发送数据有效
  12.         O_mac_tdata             <=  8'd0;   //MAC发送数据
  13.         O_mac_tdest_addr        <=  48'd0;  //MAC发送地址
  14.         tmac_addr_temp          <=  48'd0;
  15.         arp_req_pend            <=  1'b0;
  16.         dst_ip_unreachable      <=  1'b0;
  17.         arp_wait_time           <=  30'd0;
  18.         STATE                   <=  IDLE;
  19.     end
  20.     else begin
  21.         case(STATE)
  22.             IDLE:begin
  23.                 O_arp_treq_en   <=  1'b0;
  24.                 if(!I_mac_tbusy) begin//MAC层不忙
  25.                     if(I_arp_treq) begin//是否有ARP请求
  26.                         O_arp_tbusy             <=  1'b1;           //可以发送ARP包
  27.                         O_ip_tbusy              <=  1'b0;
  28.                         STATE                   <=  WAIT_ARP_PACKET;//等待ARP响应
  29.                     end
  30.                     else if(I_ip_treq && ~arp_req_pend) begin   //如果是IP请求,并且之前的ARP请求没有pend
  31.                         O_arp_tbusy             <=  1'b0;
  32.                         O_ip_tbusy              <=  1'b0;
  33.                         O_mac_cache_ren         <=  1'b1;               //如果是IP请求,先从mac cache通过IP地址获取MAC地址
  34.                         O_mac_cache_rip_addr    <=  I_ip_tdest_addr;    //通过IP地址查询MAC cache
  35.                         STATE                   <=  CHECK_MAC_CACHE;   
  36.                     end
  37.                     else begin
  38.                         O_arp_tbusy             <= 1'b0;
  39.                         O_ip_tbusy              <= 1'b0;                        
  40.                         STATE                   <= IDLE;                        
  41.                     end
  42.                 end
  43.                 else begin
  44.                     O_arp_tbusy             <= 1'b0;
  45.                     O_ip_tbusy              <= 1'b0;
  46.                     O_mac_cache_ren         <= 1'b0;
  47.                     O_mac_cache_rip_addr    <= 48'd0;
  48.                     STATE                   <= IDLE;
  49.                 end
  50.             end
  51.             CHECK_MAC_CACHE:begin//查询MAC cache,如果没有查到MAC会请求ARP层发送ARP请求
  52.                 O_mac_cache_ren         <=  1'b0;
  53.                 if(I_mac_cache_rdone) begin                     //MAC cache查询完成
  54.                     if(I_mac_cache_rdest_addr == 48'd0) begin   //如果没有查询到对应的MAC,请求ARP层发送ARP请求
  55.                         O_arp_treq_en           <=  1'b1;       //请求ARP层发送ARP
  56.                         O_ip_tbusy              <=  1'b0;
  57.                         O_arp_treq_ip_addr      <=  O_mac_cache_rip_addr;   //如果没有查询到MAC需要根据提供的IP地址请求ARP层发送ARP包获取MAC
  58.                         arp_req_pend            <=  1'b1;                   //arp请求Pend结束前不处理其他的arp请求
  59.                         STATE                   <=  IDLE;                   //回到IDLE状态,等待ARP层发送ARP包
  60.                     end
  61.                     else begin
  62.                         tmac_addr_temp          <=  I_mac_cache_rdest_addr; //从MAC cache查询到MAC地址
  63.                         O_ip_tbusy              <=  1'b1;                   //返回IP层的ACK
  64.                         O_arp_treq_en           <=  1'b0;
  65.                         arp_req_pend            <=  1'b0;
  66.                         STATE                   <=  WAIT_IP_PACKET;
  67.                     end
  68.                 end
  69.                     else
  70.                         STATE                   <=  CHECK_MAC_CACHE;
  71.             end
  72.             WAIT_ARP_REPLY:begin//等待远程主机的ARP响应(ARP层的recieve模块会接收到ARP响应)
  73.                 if(I_arp_treply_done) begin//响应
  74.                     arp_req_pend            <=  1'b0;
  75.                     arp_wait_time           <=  30'd0;
  76.                     dst_ip_unreachable      <=  1'b0;
  77.                     STATE                   <=  IDLE;
  78.                 end
  79.                 else begin
  80.                     if(arp_wait_time == ARP_TIMEOUT_VALUE) begin//超时,未收到响应
  81.                         arp_req_pend            <=  1'b1;
  82.                         O_arp_tbusy             <=  1'b0;
  83.                         O_arp_treq_en           <=  1'b1;
  84.                         O_arp_treq_ip_addr      <=  I_ip_tdest_addr;
  85.                         dst_ip_unreachable      <=  1'b1;
  86.                         arp_wait_time           <=  30'd0;
  87.                         STATE                   <=  IDLE;                       
  88.                     end
  89.                     else begin
  90.                         arp_req_pend            <=  1'b1;
  91.                         O_arp_tbusy             <=  1'b1;
  92.                         dst_ip_unreachable      <=  1'b0;
  93.                         arp_wait_time           <=  arp_wait_time + 1'b1;
  94.                         STATE                   <=  WAIT_ARP_REPLY;
  95.                     end
  96.                 end
  97.             end
  98.             WAIT_ARP_PACKET:begin//ARP包有效,打拍后直接输出给MAC层  
  99.                 if(I_arp_tvalid) begin
  100.                     O_mac_tdata_type        <=  {1'b1,I_arp_tdata_type};//2'b10:arp reply; 2'b11:arp request ;2'b01 ip
  101.                     O_mac_tvalid            <=  1'b1;
  102.                     O_mac_tdata             <=  I_arp_tdata;
  103.                     O_mac_tdest_addr        <=  I_arp_tdest_mac_addr;
  104.                     STATE                   <=  SEND_ARP_PACKET;
  105.                 end
  106.                 else begin
  107.                     O_mac_tdata_type        <=  2'd0;
  108.                     O_mac_tvalid            <=  1'b0;
  109.                     O_mac_tdata             <=  8'd0;
  110.                     O_mac_tdest_addr        <=  48'd0;
  111.                     STATE                   <=  WAIT_ARP_PACKET;                    
  112.                 end
  113.             end
  114.             SEND_ARP_PACKET:begin       //继续打拍后输出给MAC层
  115.                 if(I_arp_tvalid) begin  //如果ARP包有效
  116.                     O_mac_tvalid            <=  1'b1;
  117.                     O_mac_tdata             <=  I_arp_tdata;
  118.                     STATE                   <=  SEND_ARP_PACKET;                    
  119.                 end
  120.                 else begin
  121.                     O_arp_tbusy             <=  1'b0;
  122.                     O_mac_tdata_type        <=  2'd0;
  123.                     O_mac_tvalid            <=  1'b0;
  124.                     O_mac_tdata             <=  8'd0;
  125.                     O_mac_tdest_addr        <=  48'd0;
  126.                     if(arp_req_pend)    //如果该信号有效,代表IP层发送IP包的时候没有从本地cache查询到MAC地址,而发送的ARP请求包,因此下一步等待远程主机发送ARP响应
  127.                         STATE               <=  WAIT_ARP_REPLY;
  128.                     else
  129.                         STATE               <=  IDLE;   //如果是单纯的ARP层发送的包,到此结束           
  130.                 end
  131.             end
  132.             WAIT_IP_PACKET:begin    //IP包的传输   
  133.                 if(I_ip_tvalid) begin
  134.                     O_mac_tdata_type        <=  2'b01;
  135.                     O_mac_tvalid            <=  1'b1;
  136.                     O_mac_tdata             <=  I_ip_tdata;
  137.                     O_mac_tdest_addr        <=  tmac_addr_temp;
  138.                     STATE                   <=  SEND_IP_PACKET;
  139.                 end
  140.                 else begin         
  141.                     O_mac_tdata_type        <=  2'd0;
  142.                     O_mac_tvalid            <=  1'b0;
  143.                     O_mac_tdata             <=  8'd0;
  144.                     O_mac_tdest_addr        <=  48'd0;
  145.                     STATE                   <=  WAIT_IP_PACKET;
  146.                 end
  147.             end
  148.             SEND_IP_PACKET:begin    //IP包的传输
  149.                 if(I_ip_tvalid) begin
  150.                     O_mac_tvalid            <=  1'b1;
  151.                     O_mac_tdata             <=  I_ip_tdata;
  152.                     STATE                   <=  SEND_IP_PACKET;
  153.                 end
  154.                 else begin
  155.                     O_ip_tbusy              <= 1'b0;
  156.                     O_mac_tdata_type        <= 2'd0;
  157.                     O_mac_tvalid            <= 1'b0;
  158.                     O_mac_tdata             <= 8'd0;
  159.                     O_mac_tdest_addr        <= 48'd0;
  160.                     STATE                   <= IDLE;                    
  161.                 end
  162.             end
  163.         endcase
  164.     end
  165. end
复制代码
3.3 IP

ICMP层数据和UDP层数据都要经过IP层打包或者解包,IP层主要功能为判断数据报文类型,进行IP首部校验,添加包头或者过滤包头,处理ICMP请求。

3.3.1 IP接收模块

该模块的主要功能是接收uiip_arp_rx传入的数据包,通过首部校验判断包头的正确性,对包头进行过滤,并且提取出UDP报文和ICMP报文。

该模块的状态机转换图如图所示。

image.jpg

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状态。

  1. always@(posedge I_ip_clk or posedge I_reset) begin
  2.     if(I_reset) begin
  3.         cnt                     <= 5'd0;
  4.         ip_version              <= 4'd0;    //IP首部-版本:4位数据表示IP版本号,为4时表示IPv4,为6时表示IPv6,IPv4使用较多。
  5.         ip_header_len           <= 4'd0;    //IP首部-首部长度:4位数据表示IP首部一共有多少个32位(4个字节)数据。没有可选字段的IP首部长度为20个字节,故首部长度为5
  6.         ip_tos                  <= 8'd0;    //IP首部-服务类型:8位服务类型被划分成两个子字段:3位优先级字段和4位TOS字段,最后一位固定为0。服务类型为0时表示一般服务。
  7.         ip_pkg_len              <= 16'd0;   //IP首部-总长度:16位IP数据报总长度包括IP首部和IP数据部分,以字节为单位。利用IP首部长度和IP数据报总长度可以计算出IP数据报中数据内容的起始位置和长度
  8.         ip_packet_id            <= 16'd0;   //IP首部-ID:16位标识字段,用来标识主机发送的每一份数据报。每发送一份报文它的值就会加1
  9.         ip_packet_flag          <= 3'd0;    //IP首部-标志字段:3位标志字段的第1位是保留位,第2位表示禁止分片(1表示不分片,0允许分片),第3位标识更多分片,通常为010不分片
  10.         ip_fragment_offset      <= 13'd0;   //IP首部-片偏移:13位片偏移,在接收方进行数据报重组时用来标识分片的顺序。
  11.         ip_packet_ttl           <= 8'd0;    //IP首部-生存时间:    8位生存时间防止丢失的数据包在无休止的传播,一般被设置为64或者128
  12.         ip_packet_protocol      <= 8'd0;    //IP首部-协议:8位协议类型表示此数据报所携带上层数据使用的协议类型,ICMP为1,TCP为6,UDP为17
  13.         ip_header_checksum      <= 16'd0;   //IP首部-首部校验和:16位首部校验和,该字段只校验数据报的首部,不包含数据部分
  14.         ip_src_address          <= 32'd0;   //IP首部-源IP地址:32位发送端的IP地址
  15.         ip_dst_address          <= 32'd0;   //IP首部-目的IP地址:32位接收端的IP地址
  16.         icmp_pkg_valid          <= 1'b0;    //icmp数据报文有效信号
  17.         O_udp_ip_rvalid         <= 1'b0;    //UDP数据包有效
  18.         O_udp_ip_rdata          <= 8'd0;    //UDP数据包有效输出
  19.         STATE                   <= WAIT_IP_PACKET;
  20.     end
  21.     else begin
  22.         case(STATE)
  23.             WAIT_IP_PACKET:begin
  24.                 if(I_ip_rvalid) begin
  25.                     ip_version          <=  I_ip_rdata[7:4];    //IP首部-版本:4位数据表示IP版本号
  26.                     ip_header_len       <=  I_ip_rdata[3:0];    //IP首部-首部长度:4位数据表示IP首部一共有多少个32位(4个字节)数据
  27.                     STATE               <=  RECORD_IP_HEADER;   //下一状态,继续接收IP头部信息
  28.                 end
  29.                 else
  30.                     STATE               <=  WAIT_IP_PACKET;
  31.             end
  32.             RECORD_IP_HEADER:begin
  33.                 case(cnt)
  34.                     0:  begin   ip_tos                      <=  I_ip_rdata; cnt <=  cnt + 1'b1;end  //IP首部-服务类型:8位服务类型被划分成两个子字段:3位优先级字段和4位TOS字段,最后一位固定为0。服务类型为0时表示一般服务。
  35.                     1:  begin   ip_pkg_len[15:8]            <=  I_ip_rdata; cnt <=  cnt + 1'b1;end  //IP首部-总长度:16位IP数据报总长度包括IP首部和IP数据部分,以字节为单位。
  36.                     2:  begin   ip_pkg_len[7:0]             <=  I_ip_rdata; cnt <=  cnt + 1'b1;end  //IP首部-总长度:16位IP数据报总长度包括IP首部和IP数据部分,以字节为单位。
  37.                     3:  begin   ip_packet_id[15:8]          <=  I_ip_rdata; cnt <=  cnt + 1'b1;end  //IP首部-ID:16位标识字段,用来标识主机发送的每一份数据报。
  38.                     4:  begin   ip_packet_id[7:0]           <=  I_ip_rdata; cnt <=  cnt + 1'b1;end  //IP首部-ID:16位标识字段,用来标识主机发送的每一份数据报。
  39.                     5:  begin   
  40.                         ip_packet_flag                      <=  I_ip_rdata[7:5];    //IP首部-标志字段:3位标志字段的第1位是保留位,第2位表示禁止分片(1表示不分片,0允许分片),第3位标识更多分片,通常为010不分片
  41.                         ip_fragment_offset[12:8]            <=  I_ip_rdata[4:0];    //IP首部-片偏移:13位片偏移,在接收方进行数据报重组时用来标识分片的顺序。
  42.                         cnt <=  cnt + 1'b1;
  43.                     end
  44.                     6:  begin   ip_fragment_offset[7:0]     <=  I_ip_rdata; cnt <=  cnt + 1'b1;end  //IP首部-片偏移:13位片偏移,在接收方进行数据报重组时用来标识分片的顺序。
  45.                     7:  begin   ip_packet_ttl[7:0]          <=  I_ip_rdata; cnt <=  cnt + 1'b1;end  //IP首部-生存时间:8位生存时间防止丢失的数据包在无休止的传播,一般被设置为64或者128
  46.                     8:  begin   ip_packet_protocol[7:0]     <=  I_ip_rdata; cnt <=  cnt + 1'b1;end  //IP首部-协议:8位协议类型表示此数据报所携带上层数据使用的协议类型,ICMP为1,TCP为6,UDP为17   
  47.                     9:  begin   ip_header_checksum[15:8]    <=  I_ip_rdata; cnt <=  cnt + 1'b1;end  //IP首部-首部校验和:16位首部校验和,该字段只校验数据报的首部,不包含数据部分
  48.                     10: begin   ip_header_checksum[7:0]     <=  I_ip_rdata; cnt <=  cnt + 1'b1;end  //IP首部-首部校验和:16位首部校验和,该字段只校验数据报的首部,不包含数据部分
  49.                     11: begin   ip_src_address[31:24]       <=  I_ip_rdata; cnt <=  cnt + 1'b1;end  //IP首部-源IP地址:32位发送端的IP地址
  50.                     12: begin   ip_src_address[23:16]       <=  I_ip_rdata; cnt <=  cnt + 1'b1;end  //IP首部-源IP地址:32位发送端的IP地址
  51.                     13: begin   ip_src_address[15:8]        <=  I_ip_rdata; cnt <=  cnt + 1'b1;end  //IP首部-源IP地址:32位发送端的IP地址
  52.                     14: begin   ip_src_address[7:0]         <=  I_ip_rdata; cnt <=  cnt + 1'b1;end  //IP首部-源IP地址:32位发送端的IP地址
  53.                     15: begin   ip_dst_address[31:24]       <=  I_ip_rdata; cnt <=  cnt + 1'b1;end  //IP首部-目的IP地址:32位接收端的IP地址
  54.                     16: begin   ip_dst_address[23:16]       <=  I_ip_rdata; cnt <=  cnt + 1'b1;end  //IP首部-目的IP地址:32位接收端的IP地址
  55.                     17: begin   ip_dst_address[15:8]        <=  I_ip_rdata; cnt <=  cnt + 1'b1;end  //IP首部-目的IP地址:32位接收端的IP地址
  56.                     18: begin   
  57.                         ip_dst_address[7:0]         <=  I_ip_rdata;                         //IP首部-目的IP地址:32位接收端的IP地址
  58.                         cnt                         <=  5'd0;
  59.                         if({ip_dst_address[31:8], I_ip_rdata} == I_ip_local_addr) begin     //如果收到的IP地址和本地IP地址匹配
  60.                             if(ip_packet_protocol == ICMP_TYPE) begin                       //如果是ICMP类型
  61.                                 icmp_pkg_valid      <=  1'b1;                               //ICMP包有效           
  62.                                 STATE               <=  WAIT_PACKET_END;                    //等待传输结束状态
  63.                             end
  64.                             else if(ip_packet_protocol == UDP_TYPE) begin                   //如果是UDP包有效
  65.                                 icmp_pkg_valid      <=  1'b0;
  66.                                 STATE               <=  OUTPUT_UDP_PACKET;                  //输出UDP包到UDP协议层
  67.                             end
  68.                             else begin
  69.                                 icmp_pkg_valid      <=  1'b0;
  70.                                 STATE               <=  WAIT_PACKET_END;                    //等待传输结束状态
  71.                             end
  72.                         end
  73.                         else begin
  74.                             icmp_pkg_valid          <=  1'b0;
  75.                             STATE                   <=  WAIT_PACKET_END;                    //等待传输结束状态
  76.                         end
  77.                     end
  78.                 endcase
  79.             end
  80.             OUTPUT_UDP_PACKET:begin
  81.                 if(I_ip_rvalid) begin       //打拍后支持输出给UDP协议层
  82.                     O_udp_ip_rvalid         <=  1'b1;
  83.                     O_udp_ip_rdata          <=  I_ip_rdata;
  84.                     STATE                   <=  OUTPUT_UDP_PACKET;
  85.                 end
  86.                 else begin
  87.                     O_udp_ip_rvalid         <=  1'b0;
  88.                     O_udp_ip_rdata          <=  8'd0;
  89.                     STATE                   <=  WAIT_IP_PACKET;                 
  90.                 end     
  91.             end
  92.             WAIT_PACKET_END:begin           //等待包传输结束
  93.                 if(I_ip_rvalid)
  94.                     STATE                   <=  WAIT_PACKET_END;
  95.                 else begin
  96.                     icmp_pkg_valid          <=  1'b0;
  97.                     STATE                   <=  WAIT_IP_PACKET;
  98.                 end     
  99.             end
  100.         endcase
  101.     end     
  102. end
复制代码
3.3.2 IP发送模块

该模块的主要功能有:接收uiudp_tx发送的UDP报文和uiip_rx发送的ICMP报文信息,将其封装成IP包并发送至下层uiip_arp_tx模块。

该模块的状态机转换图如图所示。

image.jpg

IDLEuiip_tx模块处于发送空闲时,接收ICMPUDP报文发送请求,优先响应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_PACKETSEND_ICMP_PACKET:发送完报文中的有效数据后,将标识加1,回到IDLE状态。

  1. case(STATE)
  2.             IDLE:begin
  3.                 if(icmp_pkg_req & (~I_ip_tbusy)) begin          //如果有ICMP包需要发送,并且ip_arp_tx模块处于空闲(I_ip_tbusy==0代表tbuf空闲,不在发送数据)
  4.                     O_ip_treq           <=  1'b1;               //通知ip_arp_tx模块,有IP包需要发送(ICMP包也是IP包)
  5.                     O_ip_udp_tbusy      <=  1'b0;               //通知udp_layer模块,目前不能发送UDP数据包
  6.                     O_ip_taddr          <=  icmp_pkg_ip_addr;   //保存ip_rx模块接收到的icmp请求的IP地址(因为发送icmp包需要通过IP地址获取远程主机的MAC地址)
  7.                     udp_pkg_en          <=  1'b0;               //标记是否是udp包,但udp_pkg_en=1 代表ip层发送的数据为UDP包
  8.                     STATE               <=  WAIT_ACK;
  9.                 end
  10.                 else if(I_ip_udp_treq & (~I_ip_tbusy)) begin    //如果有UDP包需要发送
  11.                     O_ip_treq           <=  1'b1;               //输出ip包发送请求,给ip_arp_tx模块
  12.                     O_ip_udp_tbusy      <=  1'b0;               //通知udp_layer模块,目前不能发送UDP数据包
  13.                     O_ip_taddr          <=  I_ip_dest_addr;     //udp层提供需要发送的目的主机的IP地址
  14.                     udp_pkg_en          <=  1'b1;               //标记是否是udp包,但udp_pkg_en=1 代表ip层发送的数据为UDP包
  15.                     STATE               <=  WAIT_ACK;
  16.                 end
  17.                 else begin
  18.                     O_ip_udp_tbusy      <=  1'b0;
  19.                     O_ip_treq           <=  1'b0;
  20.                     udp_pkg_en          <=  1'b0;
  21.                     STATE               <=  IDLE;
  22.                 end
  23.             end
  24.             WAIT_ACK:begin
  25.                 if(I_ip_tbusy) begin//当发送O_ip_treq后,如果ip_arp_tx模块返回I_ip_tbusy=1 代表ip_layer可以发送IP包(UDP包和ICMP包)到ip_arp_tx模块
  26.                     O_ip_treq           <=  1'b0;
  27.                     O_ip_udp_tbusy      <=  udp_pkg_en ? 1 : 0; //如果udp_pkg_en有效代表发的是UDP包
  28.                     STATE               <=  SEND_IP_HEADER;     //发送IP帧头
  29.                 end
  30.                 else begin
  31.                     O_ip_treq           <=  1'b1;
  32.                     O_ip_udp_tbusy      <=  1'b0;
  33.                     STATE               <=  WAIT_ACK;                  
  34.                 end
  35.             end
  36.             SEND_IP_HEADER:begin//发送IP包帧头
  37.                 case(cnt)
  38.                     0   :begin
  39.                         if(I_ip_udp_tvalid | (~udp_pkg_en)) begin       //如果是UDP报文包需要发送或者udp_pkg_en==0 是ICMP报文包
  40.                             O_ip_tdata      <=  {VERSION, IHL};         //版本|首部长度(IP首部一共有多少个32bit数据)
  41.                             O_ip_tvalid     <=  1'b1;                   //通知tbuf IP数据有效
  42.                             packet_id       <=  ID_BASE + datagram_cnt; //标识,每发送1包该值加1
  43.                             TTL             <=  8'h80;                  //生存时间  
  44.                             if(!udp_pkg_en) begin                       //如果是ICMP包
  45.                                 ip_tdata_length     <=  icmp_pkg_data_len + (IHL << 2); //IP包总长度(IP数据长度+IP首部长度)
  46.                                 PROTOCOL            <=  8'h01;                          //IP包类型为 ICMP包
  47.                             end
  48.                             else begin
  49.                                 ip_tdata_length     <=  I_ip_udp_tdata_len + (IHL << 2);
  50.                                 PROTOCOL            <=  8'h11;                          //IP包类型为 UDP包
  51.                             end
  52.                             cnt             <=  cnt + 1'b1;
  53.                         end
  54.                         else
  55.                             cnt             <=  5'd0;
  56.                     end
  57.                     1   :begin  O_ip_tdata  <=  TOS;                            cnt <=  cnt + 1'b1;end//服务类型
  58.                     2   :begin  O_ip_tdata  <=  ip_tdata_length[15:8];          cnt <=  cnt + 1'b1;end//IP包总长度
  59.                     3   :begin  O_ip_tdata  <=  ip_tdata_length[7:0];           cnt <=  cnt + 1'b1;end//IP包总长度
  60.                     4   :begin  O_ip_tdata  <=  packet_id[15:8];                cnt <=  cnt + 1'b1;end//IP包标识符,每发送一份报文,其值加1
  61.                     5   :begin  O_ip_tdata  <=  packet_id[7:0];                 cnt <=  cnt + 1'b1;end//IP包标识符,每发送一份报文,其值加1
  62.                     6   :begin  O_ip_tdata  <=  {FLAG, FRAGMENT_OFFSET[12:8]};  cnt <=  cnt + 1'b1;end//标志字段3bit|片偏移共13bit
  63.                     7   :begin  O_ip_tdata  <=  FRAGMENT_OFFSET[7:0];           cnt <=  cnt + 1'b1;end//片偏移共13bit
  64.                     8   :begin  O_ip_tdata  <=  TTL;                            cnt <=  cnt + 1'b1;end//生存时间
  65.                     9   :begin  O_ip_tdata  <=  PROTOCOL;                       cnt <=  cnt + 1'b1;end//协议
  66.                     10  :begin  O_ip_tdata  <=  checksum[15:8];                 cnt <=  cnt + 1'b1;end//校验和
  67.                     11  :begin  O_ip_tdata  <=  checksum[7:0];                  cnt <=  cnt + 1'b1;end//校验和
  68.                     12  :begin  O_ip_tdata  <=  I_ip_local_addr[31:24];         cnt <=  cnt + 1'b1;end//源IP地址32bit
  69.                     13  :begin  O_ip_tdata  <=  I_ip_local_addr[23:16];         cnt <=  cnt + 1'b1;end//源IP地址32bit
  70.                     14  :begin  O_ip_tdata  <=  I_ip_local_addr[15:8];          cnt <=  cnt + 1'b1;end//源IP地址32bit
  71.                     15  :begin  O_ip_tdata  <=  I_ip_local_addr[7:0];           cnt <=  cnt + 1'b1;end//源IP地址32bit
  72.                     16  :begin                          //目的IP地址(远端主机IP地址)
  73.                         if(!udp_pkg_en)                 //ICMP报文包
  74.                             O_ip_tdata      <=  icmp_pkg_ip_addr[31:24];
  75.                         else                            //UDP报文包
  76.                             O_ip_tdata      <=  O_ip_taddr[31:24];
  77.                         cnt     <=  cnt + 1'b1;
  78.                     end                 
  79.                     17  :begin                          //目的IP地址(远端主机IP地址)
  80.                         if(!udp_pkg_en)                 //ICMP报文包
  81.                             O_ip_tdata      <=  icmp_pkg_ip_addr[23:16];
  82.                         else                            //UDP报文包
  83.                             O_ip_tdata      <=  O_ip_taddr[23:16];
  84.                         cnt     <=  cnt + 1'b1;
  85.                     end
  86.                     18  :begin                          //目的IP地址(远端主机IP地址)
  87.                         if(!udp_pkg_en) begin
  88.                             O_ip_tdata      <=  icmp_pkg_ip_addr[15:8];
  89.                             icmp_pkg_busy   <=  1'b1;//icmp_pkg_tx代码上必须时序上,在SEND_ICMP_PACKET状态输出ICMP报文包
  90.                         end
  91.                         else begin
  92.                             O_ip_tdata      <=  O_ip_taddr[15:8];
  93.                             icmp_pkg_busy   <=  1'b0;
  94.                         end
  95.                         cnt         <=  cnt + 1'b1;
  96.                     end
  97.                     19  :begin                          //目的IP地址(远端主机IP地址)
  98.                         cnt <=  5'd0;
  99.                         if(!udp_pkg_en) begin
  100.                             O_ip_tdata      <=  icmp_pkg_ip_addr[7:0];
  101.                             STATE           <=  SEND_ICMP_PACKET;
  102.                         end
  103.                         else begin
  104.                             O_ip_tdata      <=  O_ip_taddr[7:0];
  105.                             STATE           <=  SEND_UDP_PACKET;
  106.                         end
  107.                     end
  108.                     default:    cnt <=  5'd0;
  109.                 endcase
  110.             end
  111.             SEND_UDP_PACKET:begin                   //发送UDP报文包
  112.                 if(trans_data_cnt == (ip_tdata_length - 16'd20)) begin  //20个字节为IP首部,这里相等代表数据发送结束   
  113.                     O_ip_udp_tbusy      <=  1'b0;
  114.                     O_ip_tvalid         <=  1'b0;
  115.                     O_ip_tdata          <=  8'd0;
  116.                     datagram_cnt        <=  datagram_cnt + 16'h0001;    //没发送完1帧报文,该值加1
  117.                     trans_data_cnt      <=  16'd0;
  118.                     STATE               <=  IDLE;
  119.                 end
  120.                 else begin                          //发送有效的UDP报文数据部分
  121.                     O_ip_tvalid         <=  1'b1;
  122.                     O_ip_tdata          <=  shift_data_out;         //从shift移位寄存器移出数据
  123.                     trans_data_cnt      <=  trans_data_cnt + 1'b1;  //UDP报文包有效数据部分计数器
  124.                     STATE               <=  SEND_UDP_PACKET;
  125.                 end
  126.             end
  127.             SEND_ICMP_PACKET:begin                  //发送ICMP报文包
  128.                 if(icmp_pkg_valid) begin
  129.                     O_ip_tvalid         <=  1'b1;
  130.                     O_ip_tdata          <=  icmp_pkg_data;
  131.                     STATE               <=  SEND_ICMP_PACKET;
  132.                 end
  133.                 else begin
  134.                     O_ip_tvalid         <=  1'b0;
  135.                     O_ip_tdata          <=  8'd0;
  136.                     icmp_pkg_busy       <=  1'b0;
  137.                     datagram_cnt        <=  datagram_cnt + 16'h0001;//每发送完1帧报文,该值加1
  138.                     STATE               <=  IDLE;                  
  139.                 end
  140.             end
  141.         endcase
复制代码
3.3.3 IP首部校验模块

正常计算IP首部校验和时,将校验位置0。由于每两字节数据相加得到的不再产生进位结果,为最终校验和的取反,所以当计算过程中加入校验位时,相加得到的结果应当为全1。若结果不为全1,则数据校验错误,将O_check_rerror信号置为高。

  1. `timescale 1ns / 1ps
  2. module ip_header_checksum(
  3. input  wire         I_clk,
  4. input  wire         I_reset,
  5. input  wire         I_ip_rdata_valid,
  6. input  wire [7:0]   I_ip_rdata,
  7. output wire         O_checksum_rerror
  8. );
  9. reg checksum_correct;
  10. assign O_checksum_rerror = ~checksum_correct;
  11. reg [1:0]  state;
  12. reg [3:0]  cnt;
  13. wire [16:0] tmp_accum1;
  14. reg [15:0] accum1, accum2;
  15. assign tmp_accum1 = accum1 + accum2;
  16. always @(posedge I_clk or posedge I_reset) begin
  17.     if(I_reset) begin
  18.         state               <= 2'd0;
  19.         cnt                 <= 4'd0;
  20.         accum1              <= 16'd0;
  21.         accum2              <= 16'd0;      
  22.         checksum_correct    <= 1'b1;            
  23.     end
  24.     else begin
  25.         case(state)
  26.             0: begin
  27.                 if(I_ip_rdata_valid) begin
  28.                     accum1[15:8]            <= I_ip_rdata;
  29.                     state                   <= 2'd1;
  30.                 end
  31.                 else begin
  32.                     accum1[15:8]            <= 8'd0;
  33.                     state                   <= 2'd0;
  34.                 end
  35.             end
  36.             1: begin accum1[7:0]    <= I_ip_rdata; state <= 2'd2; end
  37.             2: begin        
  38.                 if(cnt == 4'd9) begin
  39.                     if((tmp_accum1[15:0] + tmp_accum1[16]) != 16'hffff)
  40.                         checksum_correct    <= 1'b0;
  41.                     cnt                 <= 4'd0;
  42.                     state               <= 2'd3;
  43.                 end
  44.                 else begin  
  45.                     accum2 <= tmp_accum1[15:0] + tmp_accum1[16];
  46.                     accum1[15:8]        <= I_ip_rdata;
  47.                     cnt                 <= cnt + 1'b1;                        
  48.                     state               <= 2'd1;
  49.                 end
  50.             end
  51.             3: begin
  52.                 accum1                  <= 16'd0;
  53.                 accum2                  <= 16'd0;
  54.                 if(I_ip_rdata_valid)
  55.                     state       <= state;
  56.                 else
  57.                     state       <= 2'd0;
  58.                 end            
  59.         endcase
  60.     end
  61. end
  62. endmodule
复制代码
3.4 ARP

该层具有接收ARP请求、发送ARP回复,和发送ARP请求、接收ARP回复的功能,并将接收到的对端的地址信息存入cache中。

3.4.1 ARP接收模块

该模块通过计数器接收28字节ARP信息字段。信息接收完成后,通过接收到的操作码判断接收到的数据包是ARP请求包还是ARP应答包。如果是应答包,将接收到的地址信息写入cache中;如果是请求包,判断目的IP地址与本地IP地址是否一致,若一致,将地址信息写入cache中,且发送至uiarp_rx模块,若不一致,则过滤该包。

  1. if(OPER == ARP_REQUEST) begin//如果是ARP请求 ARP_REQUEST = 16'h0001(16’h01 ARP请求包 ; 16’h02 ARP应答)
  2.                     if(TPA == I_ip_local_addr) begin        //比较接收到的ARP包里面的IP地址是否和本地IP地址一致
  3.                         O_arp_req_ip_addr       <=  SPA;    //发送IP(远端源IP地址)
  4.                         O_arp_req_mac_addr      <=  SHA;    //发送方MAC(远端源地址MAC)
  5.                         O_arp_req_valid         <=  1'b1;   //设置ARP请求有效(通知发送ARP发送模块发送一个ARP应答给远端主机),保存远端主机的IP地址和MAC地址到cache
  6.                         O_arp_reply_done        <=  1'b0;
  7.                     end
  8.                     else begin  
  9.                         O_arp_req_ip_addr       <=  32'd0;
  10.                         O_arp_req_mac_addr      <=  48'd0;
  11.                         O_arp_req_valid         <=  1'b0;
  12.                         O_arp_reply_done        <=  1'b0;
  13.                     end
  14.                 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应答。

  1.    case({I_arp_treq_en, I_arp_rreply_en})//本状态机实现了即便是同时有ARP应答或者ARP请求,都能确保完成发送
  2.             2'b00:begin
  3.                 if((!O_arp_treq) && (!O_arp_tvalid)) begin//没有arp_treq请求,并且arp_tvalid为0 代表没有要发送的ARP数据
  4.                     if(request_buffer_valid) begin//如果有未发送完的ARP请求,则继续发送
  5.                         OPER                    <=  ARP_REQUEST;
  6.                         TPA                     <=  ip_request_buffer;
  7.                         THA                     <=  48'd0;
  8.                         request_buffer_valid    <=  1'b0;//清除request_buffer_valid
  9.                         O_arp_treq              <=  1'b1;
  10.                     end
  11.                     else if(reply_buffer_valid) begin//如果有未发送完的ARP应答,则继续发送
  12.                         OPER                    <=  ARP_REPLY;
  13.                         TPA                     <=  ip_reply_buffer;
  14.                         THA                     <=  mac_reply_buffer;
  15.                         reply_buffer_valid      <=  1'b0;//清除request_buffer_valid
  16.                         O_arp_treq              <=  1'b1;                       
  17.                     end
  18.                 end
  19.             end
  20.             2'b01:begin//发送ARP应答
  21.                 if((!O_arp_treq) && (!O_arp_tvalid)) begin
  22.                     OPER                    <=  ARP_REPLY;
  23.                     TPA                     <=  I_arp_rreply_ip_addr;
  24.                     THA                     <=  I_arp_rreply_mac_addr;
  25.                     O_arp_treq              <=  1'b1;   
  26.                 end
  27.                 else begin//需要arp应答
  28.                     ip_reply_buffer         <=  I_arp_rreply_ip_addr;//寄存目的地址IP
  29.                     mac_reply_buffer        <=  I_arp_rreply_mac_addr;//寄存目的地址MAC   
  30.                     reply_buffer_valid      <=  1'b1;//需要发送ARP应答
  31.                 end
  32.             end
  33.             2'b10:begin//发送ARP请求,当ip_arp_tx发送IP包查询MAC没有查询到,执行ARP请求,请求远程主机提供MAC
  34.                 if((!O_arp_treq) && (!O_arp_tvalid)) begin
  35.                     OPER                    <=  ARP_REQUEST;
  36.                     TPA                     <=  I_arp_tip_addr;
  37.                     THA                     <=  48'd0;
  38.                     O_arp_treq              <=  1'b1;//ARP 发送               
  39.                 end
  40.                 else begin//arp请求包
  41.                     ip_request_buffer       <=  I_arp_tip_addr;
  42.                     request_buffer_valid    <=  1'b1;//ARP 请求有效标志
  43.                 end
  44.             end
  45.             2'b11:begin//既有ARP请求,又有ARP应答
  46.                 if((!O_arp_treq) && (!O_arp_tvalid)) begin
  47.                     OPER                    <=  ARP_REQUEST;
  48.                     TPA                     <=  I_arp_tip_addr;
  49.                     THA                     <=  48'd0;
  50.                     O_arp_treq              <=  1'b1;//ARP 发送
  51.                 end
  52.                 else begin
  53.                     ip_request_buffer       <=  I_arp_tip_addr;
  54.                     request_buffer_valid    <=  1'b1;//ARP请求有效
  55.                 end
  56.                 ip_reply_buffer         <=  I_arp_rreply_ip_addr;
  57.                 mac_reply_buffer        <=  I_arp_rreply_mac_addr;
  58.                 reply_buffer_valid      <=  1'b1;   //ARP应答有效
  59.             end
  60.         endcase
复制代码

O_arp_trequiip_arp_tx模块发送的I_arp_tbusy完成握手,则开始发送ARP包,并且通过判断ARP包的类型来确定发送的目的地址。通过计数器cnt发送28字节有效数据,再通过计数器pad_cnt在末尾补18个字节的0,即完成发送,开始等待下一次握手。

  1. case(STATE)
  2.             WAIT_BUFFER_READY:begin
  3.                 if(O_arp_treq && I_arp_tbusy) begin
  4.                     O_arp_tdata     <=  HTYPE[15:8];    //硬件类型-以太网类型
  5.                     O_arp_tvalid    <=  1'b1;           //ARP数据有效
  6.                     cnt             <=  cnt + 1'b1;     
  7.                     if(OPER == ARP_REQUEST) begin       //如果是ARP请求
  8.                         O_arp_tdest_mac_addr    <=  48'hff_ff_ff_ff_ff_ff;  //ARP目的地址为广播地址
  9.                         O_arp_ttype             <=  1'b1;                   //通知ip_arp_tx ARP类型为ARP请求
  10.                     end
  11.                     else begin
  12.                         O_arp_tdest_mac_addr    <=  THA;
  13.                         O_arp_ttype             <=  1'b0;       //通知ip_arp_tx ARP类型为ARP应答   
  14.                     end
  15.                     O_arp_treq      <=  1'b0;
  16.                     STATE           <=  SEND_ARP_PACKET;
  17.                 end
  18.                 else
  19.                     STATE           <=  WAIT_BUFFER_READY;
  20.             end
  21.             SEND_ARP_PACKET:begin
  22.                 case(cnt)
  23.                     1:  begin   O_arp_tdata <=  HTYPE[7:0];                 cnt <= cnt + 1'b1;end
  24.                     2:  begin   O_arp_tdata <=  PTYPE[15:8];                cnt <= cnt + 1'b1;end
  25.                     3:  begin   O_arp_tdata <=  PTYPE[7:0];                 cnt <= cnt + 1'b1;end
  26.                     4:  begin   O_arp_tdata <=  HLEN;                       cnt <= cnt + 1'b1;end
  27.                     5:  begin   O_arp_tdata <=  PLEN;                       cnt <= cnt + 1'b1;end
  28.                     6:  begin   O_arp_tdata <=  OPER[15:8];                 cnt <= cnt + 1'b1;end
  29.                     7:  begin   O_arp_tdata <=  OPER[7:0];                  cnt <= cnt + 1'b1;end
  30.                     8:  begin   O_arp_tdata <=  I_mac_local_addr[47:40];    cnt <= cnt + 1'b1;end
  31.                     9:  begin   O_arp_tdata <=  I_mac_local_addr[39:32];    cnt <= cnt + 1'b1;end
  32.                     10: begin   O_arp_tdata <=  I_mac_local_addr[31:24];    cnt <= cnt + 1'b1;end
  33.                     11: begin   O_arp_tdata <=  I_mac_local_addr[23:16];    cnt <= cnt + 1'b1;end
  34.                     12: begin   O_arp_tdata <=  I_mac_local_addr[15:8];     cnt <= cnt + 1'b1;end
  35.                     13: begin   O_arp_tdata <=  I_mac_local_addr[7:0];      cnt <= cnt + 1'b1;end
  36.                     14: begin   O_arp_tdata <=  I_ip_local_addr[31:24];     cnt <= cnt + 1'b1;end
  37.                     15: begin   O_arp_tdata <=  I_ip_local_addr[23:16];     cnt <= cnt + 1'b1;end
  38.                     16: begin   O_arp_tdata <=  I_ip_local_addr[15:8];      cnt <= cnt + 1'b1;end
  39.                     17: begin   O_arp_tdata <=  I_ip_local_addr[7:0];       cnt <= cnt + 1'b1;end
  40.                     18: begin   O_arp_tdata <=  THA[47:40];                 cnt <= cnt + 1'b1;end
  41.                     19: begin   O_arp_tdata <=  THA[39:32];                 cnt <= cnt + 1'b1;end
  42.                     20: begin   O_arp_tdata <=  THA[31:24];                 cnt <= cnt + 1'b1;end
  43.                     21: begin   O_arp_tdata <=  THA[23:16];                 cnt <= cnt + 1'b1;end
  44.                     22: begin   O_arp_tdata <=  THA[15:8];                  cnt <= cnt + 1'b1;end
  45.                     23: begin   O_arp_tdata <=  THA[7:0];                   cnt <= cnt + 1'b1;end
  46.                     24: begin   O_arp_tdata <=  TPA[31:24];                 cnt <= cnt + 1'b1;end
  47.                     25: begin   O_arp_tdata <=  TPA[23:16];                 cnt <= cnt + 1'b1;end
  48.                     26: begin   O_arp_tdata <=  TPA[15:8];                  cnt <= cnt + 1'b1;end
  49.                     27: begin   O_arp_tdata <=  TPA[7:0];                   cnt <= cnt + 1'b1;end                  
  50.                     28: begin
  51.                         O_arp_tdata <=  8'd0;
  52.                         if(pad_cnt == 5'd17) begin  //通过在末尾添加0以确保数据长度为46
  53.                             cnt     <=  cnt + 1'b1;
  54.                             pad_cnt <=  5'd0;
  55.                         end
  56.                         else begin
  57.                             cnt     <=  cnt;
  58.                             pad_cnt <=  pad_cnt + 1'b1;
  59.                         end
  60.                     end
  61.                     29: begin
  62.                         O_arp_tdata     <=  8'd0;
  63.                         O_arp_tvalid    <=  1'b0;
  64.                         O_arp_tdest_mac_addr    <=  48'd0;
  65.                         O_arp_ttype     <=  1'b0;
  66.                         cnt             <=  5'd0;
  67.                         STATE           <=  WAIT_BUFFER_READY;
  68.                     end
  69.                     default:begin
  70.                         O_arp_tdata     <=  8'd0;
  71.                         O_arp_tvalid    <=  1'b0;
  72.                         cnt             <=  5'd0;
  73.                         STATE           <=  WAIT_BUFFER_READY;
  74.                     end
  75.                 endcase
  76.             end
  77.         endcase
  78.     end
  79. end
复制代码
3.4.3 CACHE模块

mac_cache模块相当于一个RAM,通过IP作为地址,写入或读出MAC地址。写入mac_cache模块的地址信息远程主机发送的ARP应答包或ARP请求包中的地址,在IP层发送UDP数据报文时,需要在mac_cache中读出和IP地址对应的MAC地址。

  1. `timescale 1ns/1ps
  2. module  mac_cache
  3. (
  4.     input   wire                I_wclk,
  5.     input   wire                I_reset,
  6.     input   wire                I_wen,
  7.     input   wire    [31:0]      I_wip_addr,
  8.     input   wire    [47:0]      I_wmac_addr,
  9.     input   wire                I_rclk,
  10.     input   wire                I_ren,
  11.     input   wire    [31:0]      I_rip_addr,
  12.     output  reg     [47:0]      O_rmac_addr,
  13.     output  reg                 O_rmac_done
  14. );
  15. reg             mac_cache_flag      [0:3];
  16. reg     [31:0]  ip_address_cache    [0:3];
  17. reg     [47:0]  mac_address_cache   [0:3];
  18. reg     [1:0]   index;
  19. always@(posedge I_wclk or posedge I_reset) begin
  20.     if(I_reset) begin
  21.         mac_cache_flag[0]       <=  1'b0;
  22.         ip_address_cache[0]     <=  32'd0;
  23.         mac_address_cache[0]    <=  48'd0;
  24.         mac_cache_flag[1]       <=  1'b0;
  25.         ip_address_cache[1]     <=  32'd0;
  26.         mac_address_cache[1]    <=  48'd0;
  27.         mac_cache_flag[2]       <=  1'b0;
  28.         ip_address_cache[2]     <=  32'd0;
  29.         mac_address_cache[2]    <=  48'd0;
  30.         mac_cache_flag[3]       <=  1'b0;
  31.         ip_address_cache[3]     <=  32'd0;
  32.         mac_address_cache[3]    <=  48'd0;
  33.         index                   <=  2'd0;
  34.     end
  35.     else begin
  36.         if(I_wen) begin
  37.             if(mac_cache_flag[0] && ip_address_cache[0] == I_wip_addr)
  38.                 mac_address_cache[0]        <=  I_wmac_addr;
  39.             else if(mac_cache_flag[1] && ip_address_cache[1] == I_wip_addr)
  40.                 mac_address_cache[1]        <=  I_wmac_addr;
  41.             else if(mac_cache_flag[2] && ip_address_cache[2] == I_wip_addr)
  42.                 mac_address_cache[2]        <=  I_wmac_addr;
  43.             else if(mac_cache_flag[3] && ip_address_cache[3] == I_wip_addr)
  44.                 mac_address_cache[3]        <=  I_wmac_addr;
  45.             else begin
  46.                 mac_cache_flag[index]       <=  1'b1;
  47.                 ip_address_cache[index]     <=  I_wip_addr;
  48.                 mac_address_cache[index]    <=  I_wmac_addr;
  49.                 index                       <=  index + 1'b1;
  50.             end
  51.         end
  52.         else begin
  53.             mac_cache_flag[0]       <=  mac_cache_flag[0];
  54.             ip_address_cache[0]     <=  ip_address_cache[0];        
  55.             mac_address_cache[0]    <=  mac_address_cache[0];   
  56.             mac_cache_flag[1]       <=  mac_cache_flag[1];      
  57.             ip_address_cache[1]     <=  ip_address_cache[1];   
  58.             mac_address_cache[1]    <=  mac_address_cache[1];   
  59.             mac_cache_flag[2]       <=  mac_cache_flag[2];      
  60.             ip_address_cache[2]     <=  ip_address_cache[2];        
  61.             mac_address_cache[2]    <=  mac_address_cache[2];   
  62.             mac_cache_flag[3]       <=  mac_cache_flag[3];      
  63.             ip_address_cache[3]     <=  ip_address_cache[3];        
  64.             mac_address_cache[3]    <=  mac_address_cache[3];   
  65.         end
  66.     end
  67. end
  68. always@(posedge I_rclk or posedge I_reset) begin
  69.     if(I_reset) begin
  70.         O_rmac_addr     <=  48'd0;
  71.         O_rmac_done     <=  1'b0;
  72.     end
  73.     else begin
  74.         if(I_ren) begin
  75.             O_rmac_done     <=  1'b1;
  76.             if(mac_cache_flag[0] && I_rip_addr == ip_address_cache[0])
  77.                 O_rmac_addr <=  mac_address_cache[0];
  78.             else if(mac_cache_flag[1] && I_rip_addr == ip_address_cache[1])
  79.                 O_rmac_addr <=  mac_address_cache[1];
  80.             else if(mac_cache_flag[2] && I_rip_addr == ip_address_cache[2])
  81.                 O_rmac_addr <=  mac_address_cache[2];
  82.             else if(mac_cache_flag[3] && I_rip_addr == ip_address_cache[3])
  83.                 O_rmac_addr <=  mac_address_cache[3];
  84.             else
  85.                 O_rmac_addr <=  48'd0;
  86.         end
  87.         else begin
  88.             O_rmac_addr <=  O_rmac_addr;
  89.             O_rmac_done <=  1'b0;
  90.         end
  91.     end
  92. end
  93. endmodule
复制代码
3.5 UDP

该层实现用户数据和UDP报文的互转,相比于其它层次的设计,该层的逻辑相对简单。

3.5.1 UDP接收模块

通过计数器将UDP报文头部信息拆解,提取出端口号和长度信息。通过提取的长度对有效数据进行计数,从而得到有效数据并发送给用户端。

  1. always@(posedge I_R_udp_clk or posedge I_reset) begin
  2.     if(I_reset) begin
  3.         cnt                 <=  4'd0;
  4.         O_R_udp_valid       <=  1'b0;
  5.         O_R_udp_data        <=  8'd0;
  6.         O_R_udp_len         <=  16'd0;
  7.         udp_src_port        <=  16'd0;
  8.         udp_dest_port       <=  16'd0;
  9.         udp_pkg_len         <=  16'd0;
  10.         udp_data_cnt        <=  16'd0;
  11.     end
  12.     else if(I_udp_ip_rvalid) begin
  13.         udp_data_cnt    <=  udp_data_cnt + 1'b1;
  14.         case(cnt)
  15.             0   :begin  udp_src_port[15:8]  <=  I_udp_ip_rdata; cnt <=  cnt + 1'b1;end  //UDP接收源端口(远程主机端口)
  16.             1   :begin  udp_src_port[7:0]   <=  I_udp_ip_rdata; cnt <=  cnt + 1'b1;end  //UDP接收源端口(远程主机端口)
  17.             2   :begin  udp_dest_port[15:8] <=  I_udp_ip_rdata; cnt <=  cnt + 1'b1;end  //UDP接收目的端口(本地主机端口)
  18.             3   :begin  udp_dest_port[7:0]  <=  I_udp_ip_rdata; cnt <=  cnt + 1'b1;end  //UDP接收目的端口(本地主机端口)
  19.             4   :begin  udp_pkg_len[15:8]   <=  I_udp_ip_rdata; cnt <=  cnt + 1'b1;end  //UDP数据包长度
  20.             5   :begin  udp_pkg_len[7:0]    <=  I_udp_ip_rdata; cnt <=  cnt + 1'b1;end  //UDP数据包长度
  21.             6   :begin  cnt <=  cnt + 1'b1;end                                          //跳过检验和
  22.             7   :begin  cnt <=  cnt + 1'b1;end                                          //跳过校验和
  23.             8   :begin
  24.                 O_R_udp_valid       <=  1'b1;                                           //UDP接收数据有效
  25.                 O_R_udp_data        <=  I_udp_ip_rdata;
  26.                 O_R_udp_len         <=  udp_pkg_len - 16'd8;
  27.                 cnt                 <=  cnt + 1'b1;
  28.             end
  29.             9   :begin
  30.                 if(udp_pkg_len < 16'd26) begin
  31.                     if(udp_data_cnt == udp_pkg_len) begin
  32.                         O_R_udp_valid       <=  1'b0;
  33.                         O_R_udp_data        <=  8'd0;
  34.                         cnt                 <=  cnt + 1'b1;
  35.                     end     
  36.                     else begin
  37.                         O_R_udp_valid       <=  1'b1;
  38.                         O_R_udp_data        <=  I_udp_ip_rdata;
  39.                         cnt                 <=  cnt;                    
  40.                     end         
  41.                 end
  42.                 else begin
  43.                     O_R_udp_valid       <=  1'b1;
  44.                     O_R_udp_data        <=  I_udp_ip_rdata;
  45.                     cnt                 <=  cnt;
  46.                 end
  47.             end
  48.             10  :begin
  49.                 O_R_udp_valid       <=  1'b0;
  50.                 O_R_udp_data        <=  8'd0;
  51.                 cnt                 <=  cnt;
  52.             end
  53.             default:    cnt <=  4'd0;
  54.         endcase
  55.     end
  56.     else if(!I_udp_ip_rvalid) begin
  57.         udp_pkg_len         <=  16'd0;
  58.         udp_src_port        <=  16'd0;
  59.         udp_dest_port       <=  16'd0;
  60.         udp_data_cnt        <=  16'd0;
  61.         O_R_udp_len         <=  16'd0;
  62.         O_R_udp_data        <=  8'd0;
  63.         O_R_udp_valid       <=  1'b0;
  64.         cnt                 <=  4'd0;
  65.     end
  66. end
复制代码
3.5.2 UDP发送模块

状态机转换图如下:

image.jpg

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状态,等待下一次发送请求。

  1. always@(posedge I_W_udp_clk or posedge I_reset) begin
  2.     if(I_reset) begin
  3.         cnt                 <=  4'd0;
  4.         O_udp_ip_tvalid     <=  1'b0;
  5.         O_udp_ip_tdata      <=  8'd0;
  6.         O_udp_ip_tpkg_len   <=  16'd0;
  7.         trans_data_cnt      <=  16'd0;
  8.         STATE               <=  IDLE;
  9.     end
  10.     else begin
  11.         case(STATE)
  12.             IDLE:begin
  13.                 if(I_W_udp_req & (~I_udp_ip_tbusy)) //当有写UDP请求,并且ip_tx模块不忙(当I_udp_ip_tbusy=1代表正在ip层正在传输数据)
  14.                     STATE   <=  WAIT_ACK;           //进入WAIT_ACK
  15.                 else
  16.                     STATE   <=  IDLE;
  17.             end
  18.             WAIT_ACK:begin
  19.                 if(I_udp_ip_tbusy)                  //如果ip_tx模块准备好,代表udp_layer可以发送数据
  20.                     STATE   <=  SEND_UDP_HEADER;
  21.                 else
  22.                     STATE   <=  WAIT_ACK;
  23.             end
  24.             SEND_UDP_HEADER:begin
  25.                 case(cnt)
  26.                     0   :begin
  27.                         if(I_W_udp_valid) begin
  28.                             O_udp_ip_tvalid     <=  1'b1;                   //udp包数据有效
  29.                             O_udp_ip_tdata      <=  I_udp_local_port[15:8]; //UDP报文源端口              
  30.                             O_udp_ip_tpkg_len   <=  I_W_udp_len + 16'h0008; //UDP报文长度,其中8bytes为udp首部      
  31.          
  32.                             cnt                 <=  cnt + 1'b1;
  33.                         end
  34.                         else
  35.                             cnt                 <=  0;
  36.                     end
  37.                     1   :begin
  38.                         O_udp_ip_tdata          <=  I_udp_local_port[7:0];  //UDP报文源端口
  39.                         cnt                     <=  cnt + 1'b1;
  40.                     end
  41.                     2   :begin
  42.                         O_udp_ip_tdata          <=  I_udp_dest_port[15:8];  //UDP报文目的端口
  43.                         cnt                     <=  cnt + 1'b1;
  44.                     end
  45.                     3   :begin
  46.                         O_udp_ip_tdata          <=  I_udp_dest_port[7:0];   //UDP报文目的端口
  47.                         cnt                     <=  cnt + 1'b1;
  48.                     end
  49.                     4   :begin
  50.                         O_udp_ip_tdata          <=  O_udp_ip_tpkg_len[15:8];//UDP报文长度
  51.                         cnt                     <=  cnt + 1'b1;
  52.                     end
  53.                     5   :begin
  54.                         O_udp_ip_tdata          <=  O_udp_ip_tpkg_len[7:0]; //UDP报文长度
  55.                         cnt                     <=  cnt + 1'b1;
  56.                     end
  57.                     6   :begin
  58.                         O_udp_ip_tdata          <=  CHECKSUM[7:0];          //校验和
  59.                         cnt                     <=  cnt + 1'b1;
  60.                     end
  61.                     7   :begin
  62.                         O_udp_ip_tdata          <=  CHECKSUM [7:0];         //校验和
  63.                         cnt                     <=  0;
  64.                         STATE                   <=  SEND_UDP_PACKET;
  65.                     end
  66.                     default: cnt    <=  0;
  67.                 endcase
  68.             end
  69.             SEND_UDP_PACKET:begin
  70.                 if(trans_data_cnt != (O_udp_ip_tpkg_len - 16'd8)) begin
  71.                     O_udp_ip_tvalid     <=  1'b1;
  72.                     O_udp_ip_tdata      <=  shift_data_out;
  73.                     trans_data_cnt      <=  trans_data_cnt + 1'b1;
  74.                     STATE               <=  SEND_UDP_PACKET;
  75.                 end
  76.                 else begin
  77.                     trans_data_cnt      <=  16'd0;
  78.                     O_udp_ip_tdata      <=  8'd0;
  79.                     O_udp_ip_tvalid     <=  1'b0;
  80.                     O_udp_ip_tpkg_len   <=  16'd0;
  81.                     cnt                 <=  0;
  82.                     STATE               <=  IDLE;
  83.                 end
  84.             end
  85.             default: STATE          <=  IDLE;
  86.         endcase
  87.     end
  88. end
复制代码
3.6 ICMP

该层在程序中为IP层的子层,设计了接收ICMP请求包并回复的功能。

3.6.1 ICMP接收模块

该模块首先过滤掉ICMP报文头部,并且对头部数据进行校验,校验方式和IP首部校验相同。

  1. //ICMP报文的校验和
  2. always@(posedge I_clk or posedge I_reset) begin
  3.     if(I_reset) begin
  4.         accum1              <=  16'd0;
  5.         accum2              <=  32'd0;
  6.         checksum_state      <=  2'd0;
  7.         checksum_correct    <=  1'b1;
  8.     end
  9.     else begin
  10.         case(checksum_state)
  11.             0:begin
  12.                 if(I_icmp_pkg_valid) begin
  13.                     accum1[15:8]    <=  I_icmp_pkg_data;
  14.                     checksum_state  <=  2'd1;
  15.                 end
  16.                 else begin
  17.                     accum1[15:8]    <=  8'd0;
  18.                     checksum_state  <=  2'd0;
  19.                 end
  20.             end
  21.             1:begin
  22.                 accum1[7:0]     <=  I_icmp_pkg_data;
  23.                 checksum_state  <=  2'd2;
  24.             end
  25.             2:begin
  26.                 if(!I_icmp_pkg_valid) begin
  27.                     checksum_state      <=  2'd3;
  28.                     if((tmp_accum1[15:0] + tmp_accum1[31:16]) != 16'hffff)
  29.                         checksum_correct    <=  1'b0;
  30.                     else
  31.                         checksum_correct    <=  1'b1;
  32.                 end
  33.                 else begin
  34.                     accum2          <=  tmp_accum1;
  35.                     accum1[15:8]    <=  I_icmp_pkg_data;
  36.                     checksum_state  <=  2'd1;
  37.                 end
  38.             end
  39.             3:begin
  40.                 accum1          <=  16'd0;
  41.                 accum2          <=  32'd0;
  42.                 checksum_state  <=  2'd0;
  43.             end
  44.         endcase
  45.     end
  46. end
复制代码

将报文的附加数据存入FIFO中,并向icmp_pkg_tx模块发送ping应答信息,请求发送ping应答包。

  1. //以下模块完成ICMP报文包echo ping应答的请求,并且先缓存到ip_layer的FIFO中
  2. always@(posedge I_clk or posedge I_reset) begin
  3.     if(I_reset) begin
  4.         cnt                         <=  4'd0;
  5.         type1                       <=  8'd0;
  6.         code                        <=  8'd0;
  7.         echo_data_cnt               <=  10'd0;
  8.         checksum                    <=  16'd0;
  9.         O_icmp_req_en               <=  1'b0;
  10.         O_icmp_req_id               <=  16'd0;
  11.         O_icmp_req_sq_num           <=  16'd0;
  12.         O_icmp_ping_echo_data_valid <=  1'b0;
  13.         O_icmp_ping_echo_data       <=  8'd0;
  14.         O_icmp_req_checksum         <=  16'd0;
  15.         STATE                       <=  RECORD_ICMP_HEADER;
  16.     end
  17.     else begin
  18.         case(STATE)
  19.             RECORD_ICMP_HEADER:begin
  20.                 O_icmp_req_en       <=  1'b0;
  21.                 echo_data_cnt       <=  10'd0;
  22.                 if(I_icmp_pkg_valid)        //ICMP报文有效
  23.                     case(cnt)
  24.                         0:  begin   type1                   <=  I_icmp_pkg_data;    cnt <=  cnt + 1'b1;end  //ICMP报文首部-类型:8位数表示错误类型的差错报文或者查询类型的报告报文,一般是查询报文(0代表回显应答(ping应答);1代表查
  25.                         1:  begin   code                    <=  I_icmp_pkg_data;    cnt <=  cnt + 1'b1;end  //ICMP报文首部-类型:代码占用8位数据,根据ICMP差错报文的类型,进一步分析错误的原因
  26.                         2:  begin   checksum[15:8]          <=  I_icmp_pkg_data;    cnt <=  cnt + 1'b1;end  //ICMP报文首部-校验和:16位校验和的计算方法与IP首部校验和计算方法一致,该校验和需要对ICMP首部和ICMP数据做校验
  27.                         3:  begin   checksum[7:0]           <=  I_icmp_pkg_data;    cnt <=  cnt + 1'b1;end  //ICMP报文首部-校验和:16位校验和的计算方法与IP首部校验和计算方法一致,该校验和需要对ICMP首部和ICMP数据做校验
  28.                         4:  begin   O_icmp_req_id[15:8]     <=  I_icmp_pkg_data;    cnt <=  cnt + 1'b1;end  //ICMP报文首部-标识符:16位标识符对每一个发送的数据报进行标识
  29.                         5:  begin   O_icmp_req_id[7:0]      <=  I_icmp_pkg_data;    cnt <=  cnt + 1'b1;end  //ICMP报文首部-标识符:16位标识符对每一个发送的数据报进行标识
  30.                         6:  begin   O_icmp_req_sq_num[15:8] <=  I_icmp_pkg_data;    cnt <=  cnt + 1'b1;end  //ICMP报文首部-序列号:16位对发送的每一个数据报文进行编号
  31.                         7:  begin   O_icmp_req_sq_num[7:0]  <=  I_icmp_pkg_data;    cnt <=  cnt + 1'b1;end  //ICMP报文首部-序列号:16位对发送的每一个数据报文进行编号
  32.                         8:  begin
  33.                             if(type1 == PING_REQUEST && code == 8'h00) begin        //如果是远端主机发的ping请求包,那么本地主机需要返回一个ping应答包      
  34.                                 O_icmp_ping_echo_data_valid <=  1'b1;               //ping应答有效
  35.                                 O_icmp_ping_echo_data       <=  I_icmp_pkg_data;
  36.                             end
  37.                             else begin
  38.                                 O_icmp_ping_echo_data_valid <=  1'b0;
  39.                                 O_icmp_ping_echo_data       <=  8'd0;
  40.                             end
  41.                             cnt     <=  4'd0;
  42.                             STATE   <=  WAIT_PACKET_END;
  43.                         end
  44.                         default:    STATE   <=  RECORD_ICMP_HEADER;
  45.                     endcase
  46.                 else
  47.                     STATE   <=  RECORD_ICMP_HEADER;
  48.             end
  49.             WAIT_PACKET_END:begin
  50.                 if(I_icmp_pkg_valid) begin          //继续接收ICMP 报文
  51.                     if(O_icmp_ping_echo_data_valid) //ping应答有效
  52.                         echo_data_cnt   <=  echo_data_cnt + 1'b1;//ICMP包计数器
  53.                     else
  54.                         echo_data_cnt   <=  10'd0;
  55.                     O_icmp_ping_echo_data_valid <=  O_icmp_ping_echo_data_valid;
  56.                     O_icmp_ping_echo_data       <=  I_icmp_pkg_data;
  57.                     STATE               <=  WAIT_PACKET_END;
  58.                 end
  59.                 else begin
  60.                     if(O_icmp_ping_echo_data_valid) begin
  61.                         O_icmp_req_en       <=  1'b1;           //通知ip_tx模块接收到ICMP报文包ping请求,并且发送一个echo ping应答
  62.                         O_icmp_req_checksum <=  checksum_temp;  //输出校验和
  63.                     end
  64.                     else begin
  65.                         O_icmp_req_en       <=  1'b0;
  66.                         O_icmp_req_checksum <=  16'd0;                     
  67.                     end
  68.                     echo_data_cnt               <=  echo_data_cnt;
  69.                     O_icmp_ping_echo_data_valid <=  1'b0;
  70.                     O_icmp_ping_echo_data       <=  8'd0;
  71.                     STATE                       <=  RECORD_ICMP_HEADER;
  72.                 end
  73.             end
  74.         endcase
  75.     end
  76. end
复制代码
3.6.2 ICMP发送模块

该模块收到icmp_pkg_ctrl模块发送的ping应答包使能信号,寄存接收到的信息包括标识符、序列号、校验和、地址和数据长度,通过计数器添加报文头部,再将附加数据从FIFO中读出,组成ping应答包发送到ip_tx模块。

  1. always@(posedge I_clk or posedge I_reset) begin
  2.     if(I_reset) begin
  3.         cnt1                    <=  4'd0;
  4.         cnt2                    <=  10'd0;
  5.         request_id              <=  16'd0;
  6.         request_sq_num          <=  16'd0;
  7.         request_ip_taddress     <=  32'd0;
  8.         checksum                <=  16'd0;
  9.         echo_data_length        <=  10'd0;
  10.         O_icmp_pkg_req          <=  1'b0;
  11.         O_icmp_pkg_valid        <=  1'b0;
  12.         O_icmp_pkg_data         <=  8'd0;
  13.         O_icmp_ping_echo_ren    <=  1'b0;
  14.         STATE                   <=  WAIT_ICMP_PACKET;
  15.     end
  16.     else begin
  17.         case(STATE)
  18.             WAIT_ICMP_PACKET:begin
  19.                 if(I_icmp_req_en) begin//当接收到ICMP echo ping包,先保存该包的基本信息到寄存器
  20.                     request_id              <=  I_icmp_req_id;              //ICMP包的标识符
  21.                     request_sq_num          <=  I_icmp_req_sq_num;          //ICMP包的序列号
  22.                     request_ip_taddress     <=  I_icmp_req_ip_addr;         //ICMP包的地址
  23.                     checksum                <=  I_icmp_req_checksum;        //ICMP包的校验和
  24.                     echo_data_length        <=  I_icmp_ping_echo_data_len;  //ICMP包的长度      
  25.                     O_icmp_pkg_req          <=  1'b1;                       //请求ip_tx模块发送部分,发送ICMP报文
  26.                     STATE                   <=  WAIT_PACKET_SEND;           //发送ICMP包状态
  27.                 end
  28.                 else begin
  29.                     request_id              <=  16'd0;
  30.                     request_sq_num          <=  16'd0;
  31.                     request_ip_taddress     <=  32'd0;
  32.                     checksum                <=  16'd0;
  33.                     echo_data_length        <=  10'd0;
  34.                     O_icmp_pkg_req          <=  1'b0;
  35.                     STATE                   <=  WAIT_ICMP_PACKET;                  
  36.                 end
  37.             end
  38.             WAIT_PACKET_SEND:begin
  39.                 if(I_icmp_pkg_busy) begin//该信号来自ip_tx模块,当有效代表ip_tx模块已经开始准备发送ICMP包,这里需要对ip_tx代码部分时序逻辑确保数据正确给到ip_tx模块
  40.                     O_icmp_pkg_req          <=  1'b0;
  41.                     O_icmp_pkg_valid        <=  1'b1;
  42.                     O_icmp_pkg_data         <=  PING_REPLY_TYPE;//回显应答(ping应答)的类型
  43.                     STATE                   <=  SEND_PACKET;
  44.                 end
  45.                 else begin
  46.                     O_icmp_pkg_req          <=  1'b1;
  47.                     O_icmp_pkg_valid        <=  1'b0;
  48.                     O_icmp_pkg_data         <=  8'd0;
  49.                     STATE                   <=  WAIT_PACKET_SEND;
  50.                 end
  51.             end
  52.             SEND_PACKET:begin
  53.                 case(cnt1)
  54.                     0   :begin  O_icmp_pkg_data <=  8'h00;                  cnt1    <=  cnt1 + 1'b1;end//回显应答(ping应答)的代码
  55.                     1   :begin  O_icmp_pkg_data <=  checksum[15:8];         cnt1    <=  cnt1 + 1'b1;end//ICMP报文包校验和,直接获取远程主机发送的Ping包校验和
  56.                     2   :begin  O_icmp_pkg_data <=  checksum[7:0];          cnt1    <=  cnt1 + 1'b1;end//ICMP报文包校验和,直接获取远程主机发送的Ping包校验和
  57.                     3   :begin  O_icmp_pkg_data <=  request_id[15:8];       cnt1    <=  cnt1 + 1'b1;end//ICMP报文标识符,直接获取远程主机发送的Ping包标识符
  58.                     4   :begin  O_icmp_pkg_data <=  request_id[7:0];        cnt1    <=  cnt1 + 1'b1;end//ICMP报文标识符,直接获取远程主机发送的Ping包标识符
  59.                     5   :begin  O_icmp_pkg_data <=  request_sq_num[15:8];   cnt1    <=  cnt1 + 1'b1;end//ICMP报文编码,直接获取远程主机发送的Ping序列号
  60.                     6   :begin  //从echo FIFO中读取ICMP echo报文的数据部分
  61.                         O_icmp_pkg_data         <=  request_sq_num[7:0];   
  62.                         cnt1                    <=  cnt1 + 1'b1;
  63.                         O_icmp_ping_echo_ren    <=  1'b1;
  64.                     end
  65.                     7   :begin//ICMP报文包的数据有效部分
  66.                         O_icmp_pkg_valid        <=  1'b1;
  67.                         O_icmp_pkg_data         <=  I_icmp_ping_echo_data;
  68.                         if(cnt2 == (echo_data_length - 1)) begin
  69.                             cnt2                    <=  10'd0;
  70.                             O_icmp_ping_echo_ren    <=  1'b0;
  71.                             cnt1                    <=  cnt1 + 1'b1;
  72.                         end
  73.                         else begin
  74.                             cnt2                    <=  cnt2 + 1'b1;
  75.                             O_icmp_ping_echo_ren    <=  1'b1;
  76.                             cnt1                    <=  cnt1;                           
  77.                         end
  78.                     end
  79.                     8   :begin
  80.                         cnt1                    <=  4'd0;
  81.                         O_icmp_pkg_data         <=  8'd0;
  82.                         O_icmp_pkg_valid        <=  1'b0;
  83.                         STATE                   <=  WAIT_ICMP_PACKET;
  84.                     end
  85.                     default:;
  86.                 endcase
  87.             end
  88.         endcase
  89.     end
  90. end
复制代码
4 仿真验证

仿真代码的顶层如下:

  1. `timescale 1ns / 1ps
  2. module sim_top;
  3. reg         I_reset;
  4. reg         I_clk;
  5. wire        b_r_udp_valid;
  6. wire [7 :0] b_r_udp_data;
  7. wire [15:0] b_r_udp_data_len;
  8. wire [15:0] b_r_udp_src_port;
  9. wire        O_a_ip_rx_error;
  10. wire        O_a_mac_rx_error;
  11. wire        O_b_ip_rx_error;
  12. wire        O_b_mac_rx_error;
  13. test_udp_loop test_udp_loop_u(
  14. .I_reset            (I_reset),
  15. .I_clk              (I_clk),
  16. .b_r_udp_valid      (b_r_udp_valid),
  17. .b_r_udp_data       (b_r_udp_data),
  18. .b_r_udp_data_len   (b_r_udp_data_len),
  19. .b_r_udp_src_port   (b_r_udp_src_port),
  20. .O_a_ip_rx_error    (O_a_ip_rx_error),
  21. .O_a_mac_rx_error   (O_a_mac_rx_error),
  22. .O_b_ip_rx_error    (O_b_ip_rx_error),
  23. .O_b_mac_rx_error   (O_b_mac_rx_error)
  24. );
  25. initial begin
  26.     I_clk = 0;
  27.     I_reset = 1;
  28.     #2000;
  29.     I_reset = 0;
  30. end
  31. always #4 I_clk <= ~I_clk;     
  32. endmodule
复制代码
4.1 数据通信仿真

例化两个UDP协议栈模块,分别为主机A和主机B,主机A将用户端输入的数据打包,通过GMII接口发送给主机B,主机B将数据解包后输出至用户端,观察各层的信号变化。

4.1.1 仿真代码编写

初始状态机为WAIT_UDP_RDY,等待主机AO_W_udp_busy信号为低时,用户端将I_W_udp_req信号拉高,状态机进入WAIT_UDP_ACK状态。待主机AO_W_udp_busy拉高时,用户端将I_W_udp_req拉低,状态机跳转至SEND_DATA状态,计数器开始计数,发送的数据为计数器的低8位。当数据都发送完毕时,状态机跳回WAIT_UDP_RDY状态,等待下一次数据的发送。仿真代码如下:

  1. `timescale 1ns / 1ps
  2. module test_udp_loop(
  3. input  wire        I_reset,
  4. input  wire        I_clk,
  5. output wire        b_r_udp_valid,
  6. output wire [7 :0] b_r_udp_data,
  7. output wire [15:0] b_r_udp_data_len,
  8. output wire [15:0] b_r_udp_src_port,
  9. output wire        O_a_ip_rx_error,
  10. output wire        O_a_mac_rx_error,
  11. output wire        O_b_ip_rx_error,
  12. output wire        O_b_mac_rx_error
  13. );
  14. wire        a_w_udp_rdy;
  15. reg         a_w_udp_req;
  16. reg         a_w_udp_valid;
  17. wire [7:0]  a_w_udp_data;
  18. reg  [15:0] a_w_udp_len;
  19. reg         a_w_udp_data_read;
  20. wire        b_w_udp_rdy;
  21. reg         b_w_udp_req;
  22. reg         b_w_udp_valid;
  23. wire [7:0]  b_w_udp_data;
  24. reg  [15:0] b_w_udp_len;
  25. reg         b_w_udp_data_read;
  26. wire        a_r_udp_valid;
  27. wire [7 :0] a_r_udp_data;
  28. wire [15:0] a_r_udp_data_len;
  29. wire [15:0] a_r_udp_src_port;
  30. reg  [9 :0] test_data;
  31. reg  [1 :0] STATE;
  32. parameter  WAIT_UDP_READY = 0;
  33. parameter  WAIT_UDP_ACK = 1;
  34. parameter  SEND_DATA = 2;
  35. always@(posedge I_clk or posedge I_reset)begin
  36.    if(I_reset) begin
  37.         a_w_udp_req         <= 1'b0;
  38.         a_w_udp_valid       <= 1'b0;
  39.         a_w_udp_len         <= 16'd0;
  40.         test_data           <= 10'd0;
  41.         STATE               <= WAIT_UDP_READY;
  42.         
  43.     end
  44.     else begin  
  45.         case(STATE)
  46.             WAIT_UDP_READY:begin
  47.                 if(~a_w_udp_rdy) begin
  48.                     a_w_udp_req             <= 1'b1;
  49.                     STATE                   <= WAIT_UDP_ACK;
  50.                 end
  51.                 else begin
  52.                     a_w_udp_req             <= 1'b0;
  53.                     STATE                   <= WAIT_UDP_READY;
  54.                 end
  55.             end
  56.             WAIT_UDP_ACK:begin
  57.                 if(a_w_udp_rdy) begin
  58.                     a_w_udp_len             <= 16'd768;
  59.                     a_w_udp_valid           <= 1'b1;
  60.                     a_w_udp_req             <= 1'b0;               
  61.                     STATE                   <= SEND_DATA;
  62.                 end
  63.                 else
  64.                     STATE <= WAIT_UDP_ACK;
  65.             end
  66.             SEND_DATA:begin
  67.                 if(test_data == 10'd767) begin
  68.                     a_w_udp_valid           <= 1'b0;
  69.                     a_w_udp_len             <= 16'd0;
  70.                     test_data               <= 0;
  71.                     STATE                   <= WAIT_UDP_READY;
  72.                 end
  73.                 else begin
  74.                     a_w_udp_valid           <= 1'b1;
  75.                     test_data               <= test_data + 1'b1;
  76.                     STATE                   <= SEND_DATA;
  77.                 end
  78.             end
  79.         endcase
  80.     end
  81. end
  82. //以下实现A发送,B接收的仿真测试
  83. wire [7:0]  a_gmii_tdata;
  84. wire        a_gmii_tvalid;
  85. wire [7:0]  b_gmii_tdata;
  86. wire        b_gmii_tvalid;
  87. assign  a_w_udp_data = test_data[7:0];
  88. udp_stack #
  89. (
  90. .CRC_GEN_EN             (1'b1),
  91. .INTER_FRAME_GAP        (4'd12)
  92. )
  93. A_udp_stack
  94. (
  95. .I_uclk                 (I_clk),
  96. .I_reset                (I_reset),
  97. .I_mac_local_addr       (48'h0123456789a2),//本地MAC地址   
  98. .I_udp_local_port       (16'd6002       ), //本地端口号
  99. .I_ip_local_addr        (32'hc0a88902   ), //本地ip地址
  100. .I_udp_dest_port        (16'd6001       ), //目的端口
  101. .I_ip_dest_addr         (32'hc0a88901   ), //目的IP地址
  102. .O_W_udp_busy           (a_w_udp_rdy),
  103. .I_W_udp_req            (a_w_udp_req),
  104. .I_W_udp_valid          (a_w_udp_valid),
  105. .I_W_udp_data           (a_w_udp_data),
  106. .I_W_udp_len            (a_w_udp_len),
  107. .O_R_udp_valid          (),
  108. .O_R_udp_data           (),
  109. .O_R_udp_len            (),
  110. .O_R_udp_src_port       (),
  111. .I_gmii_rclk            (I_clk),
  112. .I_gmii_rvalid          (b_gmii_tvalid),
  113. .I_gmii_rdata           (b_gmii_tdata),
  114. .I_gmii_tclk            (I_clk),
  115. .O_gmii_tvalid          (a_gmii_tvalid),
  116. .O_gmii_tdata           (a_gmii_tdata),
  117. .O_ip_rerror                (O_a_ip_rx_error),
  118. .O_mac_rerror           (O_a_mac_rx_error)
  119. );
  120. udp_stack #
  121. (
  122. .CRC_GEN_EN             (1'b1),
  123. .INTER_FRAME_GAP        (4'd12)
  124. )
  125. B_udp_stack
  126. (
  127. .I_uclk                 (I_clk),
  128. .I_reset                (I_reset),
  129. .I_mac_local_addr       (48'h0123456789a1),//本地MAC地址   
  130. .I_udp_local_port       (16'd6001       ), //本地端口号
  131. .I_ip_local_addr        (32'hc0a88901   ), //本地ip地址
  132. .I_udp_dest_port        (16'd6002   ), //目的端口
  133. .I_ip_dest_addr         (32'hc0a88902   ), //目的IP地址
  134. .O_W_udp_busy           (),
  135. .I_W_udp_req            (0),
  136. .I_W_udp_valid          (0),
  137. .I_W_udp_data           (0),
  138. .I_W_udp_len            (0),
  139. .O_R_udp_valid          (b_r_udp_valid),
  140. .O_R_udp_data           (b_r_udp_data),
  141. .O_R_udp_len            (b_r_udp_data_len),
  142. .O_R_udp_src_port       (b_r_udp_src_port),
  143. .I_gmii_rclk            (I_clk),
  144. .I_gmii_rvalid          (a_gmii_tvalid),
  145. .I_gmii_rdata           (a_gmii_tdata),
  146. .I_gmii_tclk            (I_clk),
  147. .O_gmii_tvalid          (b_gmii_tvalid),
  148. .O_gmii_tdata           (b_gmii_tdata),
  149. .O_ip_rerror                (O_b_ip_rx_error),
  150. .O_mac_rerror           (O_b_mac_rx_error)
  151. );
  152. endmodule
复制代码
4.1.2 ARP请求仿真结果

主机A将数据打包发送给主机B时,由于主机Acache中查询不到主机BMAC地址,主机A会先发送一个ARP请求包给主机B

image.jpg

当数据帧发送至ip_arp_tx模块时,该模块向ARP层的cache发送一个MAC地址查询请求I_mac_cache_ren和需要查询的IP地址I_mac_cache_rip_addr,查询结束后O_mac_cache_rdone拉高,返回的MAC地址为48h0,说明未查询到MAC地址,ip_arp_tx模块将I_arp_treq_en拉高,准备发送ARP广播。arp_tx模块接收到I_arp_treq_en高电平时,发送ARP请求O_arp_reqip_arp_tx模块将I_arp_busy拉高,表示握手成功,arp_tx模块开始组ARP广播包。下图为GMII接口发送ARP广播仿真波形图。

image.jpg

最后信号经过MAC层组帧后通过GMII接口发送至主机B

4.1.3 ARP应答仿真结果

主机B接收到ARP请求包后,会将ARP包中的MAC地址解析,并将自己的MAC地址通过ARP应答包发送给主机A。主机A收到ARP应答包后,将主机BMAC地址存入cache中。下图为解包后发送至arp_rx模块的数据。

image.jpg

MAC层解析数据的类型为ARP包,将该包数据通过ip_arp_rx模块发送给arp_rx模块,解析出IP地址、MAC地址,并将该包识别为ARP请求包。arp_rx发送arp_req_valid,信号给arp_tx模块,请求发送ARP应答包,同时将ARP请求包中的主机AMAC地址写入cache中。arp_tx模块接收到应答请求并寄存,向ip_arp_tx模块发送请求,ip_arp_tx模块将busy信号拉高以表示握手成功。busy信号为高后,arp_tx模块组ARP应答包,将本地MAC地址等信息发送至下层协议。

ARP层发出的应答数据包经MAC层组帧,通过GMII接口发送至主机A。下图为主机A GMII接口接收到的ARP请求包数据。

image.jpg

主机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包的仿真波形图

image.jpg

下图为IP层组IP包的仿真波形图

image.jpg

由于数据都写入了data_fifo,当数据有效数据全部写入完成后,才会将数据读出,所以图中组的MAC包是上一个数据包正在发送的数据包IP头部的标识为16h0000,而从上层传入的IP数据包包头标识为16h0001

下图为MAC层组MAC包的仿真波形图

image.jpg
4.1.5 UDP接收仿真结果

主机BGMII接口接收到主机A发送的数据包,会将数据先存入FIFO做跨时钟域,当数据全部接收完成后,才会将数据读出,图中正在接收的数据包IP头部标识为16h0003,发送至上层协议的数据包标识为16h0002,即发送至IP层的数据包为上一个帧。下图为过滤MAC头部的仿真波形图

image.jpg

通过计数器去掉IP头部和UDP头部,最终得到有效数据,和发送的数据一致,如图所示。

image.jpg

4.2 PAUSE流控仿真

发送数据的内容和速率与上一节的仿真中一致,每隔一定的时间,在用户端组一个PAUSE帧发送至主机A,暂停时间设置为16h007F,即主机A实际暂停时间为8128个时钟周期(7Fh<< 6 = 8128d))。

4.2.1 仿真代码编写

通过状态机和计数器,控制PAUSE帧发送到主机A的时序和间隔,同时不断地向主机A的用户端写数据。仿真代码如下:

  1. always@(posedge I_clk or posedge I_reset)begin
  2.     if(I_reset) begin
  3.         pause_data      <=      8'd0;
  4.         pause_vld       <=      1'b0;
  5.         cnt1            <=      'd0;
  6.         data_cnt        <=      'd0;
  7.         STATE2          <=      WAIT_PAUSE_RDY;
  8.     end
  9.     else begin
  10.         case(STATE2)
  11.             WAIT_PAUSE_RDY:begin
  12.                 data_cnt    <=  'd0;
  13.                 if(cnt1 == 16'd5000) begin
  14.                     cnt1    <=  'd0;
  15.                     STATE2  <=  SEND_PAUSE_DATA;
  16.                 end
  17.                 else begin
  18.                     cnt1    <=  cnt1 + 1'b1;
  19.                     STATE2  <=  WAIT_PAUSE_RDY;
  20.                 end
  21.             end
  22.             SEND_PAUSE_DATA:begin
  23.                 data_cnt    <=  data_cnt + 1'b1;
  24.                 case(data_cnt)
  25.                     0   :begin  pause_data  <=  8'h55;  pause_vld   <=  1'b1;end
  26.                     1   :begin  pause_data  <=  8'h55;end
  27.                     2   :begin  pause_data  <=  8'h55;end
  28.                     3   :begin  pause_data  <=  8'h55;end
  29.                     4   :begin  pause_data  <=  8'h55;end
  30.                     5   :begin  pause_data  <=  8'h55;end
  31.                     6   :begin  pause_data  <=  8'h55;end
  32.                     7   :begin  pause_data  <=  8'hd5;end
  33.                     8   :begin  pause_data  <=  8'h01;end
  34.                     9   :begin  pause_data  <=  8'h80;end
  35.                     10  :begin  pause_data  <=  8'hc2;end
  36.                     11  :begin  pause_data  <=  8'h00;end
  37.                     12  :begin  pause_data  <=  8'h00;end
  38.                     13  :begin  pause_data  <=  8'h01;end//pause广播地址
  39.                     14  :begin  pause_data  <=  8'h01;end
  40.                     15  :begin  pause_data  <=  8'h23;end
  41.                     16  :begin  pause_data  <=  8'h45;end
  42.                     17  :begin  pause_data  <=  8'h67;end
  43.                     18  :begin  pause_data  <=  8'h89;end
  44.                     19  :begin  pause_data  <=  8'ha1;end
  45.                     20  :begin  pause_data  <=  8'h88;end
  46.                     21  :begin  pause_data  <=  8'h08;end
  47.                     22  :begin  pause_data  <=  8'h00;end
  48.                     23  :begin  pause_data  <=  8'h01;end
  49.                     24  :begin  pause_data  <=  8'h00;end
  50.                     25  :begin  pause_data  <=  8'h7f;end
  51.                     26  :begin  pause_data  <=  8'hff;end
  52.                     27  :begin  pause_data  <=  8'hff;end
  53.                     36  :begin  pause_data  <=  8'h15;end
  54.                     37  :begin  pause_data  <=  8'hbf;end
  55.                     38  :begin  pause_data  <=  8'h4b;end
  56.                     39  :begin  pause_data  <=  8'h6c;end
  57.                     40  :begin  
  58.                         pause_data  <=  8'h00;
  59.                         pause_vld   <=  1'b0;
  60.                         STATE2  <=  WAIT_PAUSE_RDY;
  61.                     end
  62.                     default:pause_data  <=  8'h00;
  63.                 endcase
  64.             end
  65.             default: begin
  66.                 pause_data      <=      8'd0;
  67.                 pause_vld       <=      1'b0;
  68.                 cnt1            <=      'd0;
  69.                 data_cnt        <=      'd0;
  70.                 STATE2  <=  WAIT_PAUSE_RDY;
  71.             end
  72.         endcase
  73.     end
  74. end
复制代码
4.2.2 PAUSE流控仿真结果

主机A接收到PAUSE帧后,会将PAUSE帧里的信息送入mac_tx_frame_ctrl模块中进行解析,得到暂停时间,并发送给至mac_tx模块,如图所示。

image.jpg

mac_tx模块中的读状态机进入帧间隔状态时,通过计数器将pause_flag拉高一定的时间。检测到下一帧发送方的MAC地址与PAUSE帧发送方的MAC地址相同,均为48h0123456789a1,进入暂停状态。

仿真波形图如图,由下图可知,pause_flag拉高的时间为65024ns,即为8128个时钟周期。

image.jpg
4.3 ICMP层仿真

向主机A发送一个ping请求包,主机A成功接收到ping请求包后,发送一个ping应答包,其中的额外数据与请求包相同。

4.3.1 仿真代码编写

组一个ping请求包给主机A,仿真代码与组pause帧类似,其中IP头部的协议类型为8h01ICMP头部的type字段为8h08code字段为8h00,表示主动请求。

4.3.2 ICMP回显应答仿真结果

如图所示,ip_rx模块接收到ICMP数据报文会将标识符、序列号、校验和等信息拆解出来,并将有效数据存入FIFO。一帧数据接收完成后,将发送ping应答请求信号给ip_tx模块。

image.jpg

image.jpg

请求信号icmp_req_en拉高后,icmp_pkg_tx模块中的icmp_pkg_req信号也拉高,并发送给ip_tx模块,等待ip_tx模块将icmp_pkg_req拉高,表示握手成功,开始发送icmp回显应答数据。ping应答包的type字段为8h00code字段为8h00,表示回显应答,其有效数据和接收到的ping请求包额外数据保持一致。

image.jpg
5 上板调试
5.1 硬件连接

本次实验的物理层选择千兆以太网RJ45网口来验证该UDP协议栈的功能。首先将开发板的RJ45网口通过网线和PC连接,插入开发板的电源和下载器,硬件连接图如图5.1所示。打开电源开关,等待网口的黄灯亮起,PHY芯片自协商成功。可以进行下一步操作。

image.jpg

5.2 PC设置
5.2.1 PC地址查询

为实现以太网的各种通信测试,我们需要先查找到PC端以太网网卡的物理地址和IP地址,以进行组包发送和对结果进行验证。

windows开始菜单界面的搜索框中输入cmd,找到命令提示符并打开,如图5.2所示。

image.jpg

打开命令提示符界面后,在界面中输入”ipconfig -all”,得到网卡的各种信息,如图所示,其中”CC-28-AA-06-33-56为网卡的MAC地址,其数据格式为16进制,192.168.137.1为网卡的IP地址,其数据格式为10进制。子网掩码255.255.255.0表示若要使FPGAPC之间建立正常的以太网通信,FPGAPCIP地址的高位的三个字节必须相等,如192.168.137.xxx”。

image.jpg

接下来输入”arp -a查询PC网卡中ARP链表缓存的地址,通过将IP地址和MAC地址绑定到ARP静态链表上,可以不使用ARP功能就能实现以太网UDP通信,由于本UDP协议栈实现了ARP应答和主动请求功能,不需要绑定地址。可以看到,网卡上并没有缓存其它动态地址。

image.jpg
5.2.2 网络调试软件

本次实验需要用到三种网络调试软件,分别为wiresharknetassist、科来数据包生成器,接下来介绍这几款软件的界面和功能。

wireshark的界面如图所示,它可以抓取到不同类型的数据包如ARP包、ICMP包、UDP包,并显示除帧头和校验位以外的数据包的全部内容。

image.jpg

Netassist的界面如图所示,该工具可以通过绑定IP地址,对UDP数据进行发送或接收,并且显示接收的具体内容和收发数据的计数。

image.jpg

科来数据包生成器的界面如图所示,它可以组任意类型的数据包并可以将不同的数据包按照一定的间隔顺序发送。

image.jpg

5.3 UDP回环验证

netassist连续发送数据给FPGAFPGA将接收到的数据传回,以此测试UDP协议栈的ARP应答功能以及UDP通信功能。

在代码顶层文件中将接收到的UDP数据包有效数据缓存中,待udp_txtbusy信号为低时,发送一次请求,握手成功后,发送接收到的一包数据,这样就能实现UDP数据的回环。在顶层中设置FPGAMAC地址为48h01_23_45_67_89_abIP地址为192.168.137.1

5.3.1 wireshark抓取PC发送的ARP广播

由于ARP链表中没有缓存FPGA的地址,PC在发送数据时,找不到IP地址对应的MAC地址,于是PC会发送ARP广播,请求获取FPGA上设置的物理地址。先不要将程序下载进芯片中,通过wireshark抓取ARP广播,抓取到的数据包信息如图所示。

image.jpg

由上图可以得知,发送方发送的目的MAC地址为48hff_ff_ff_ff_ff_ff,类型为16h0806,操作码为16h0001,表示ARP请求,请求的IP地址为192.168.137.2。接着将程序下载进FPGA中,重新发送数据,wireshark抓取到的数据包如图所示。

可以看到,FPGAPC发送的ARP广播回复了一个ARP应答包,将自己的MAC地址发送出去,操作码为16h0002,表示ARP应答。

image.jpg
5.3.2 netassist收发UDP数据包

使用netassist接收和发送的数据如图所示,可以看出每次发送和传回的数据内容保持一致,并且接收和发送的数据包数量和有效数据数量均相等,表示程序中UDP发送和接收的部分功能正确。

image.jpg

5.3.3 通过PING命令发送ICMP请求

打开WINDOWS命令提示符界面,输入ping 192.168.137.2 -t命令,PC会向FPGA持续发送ICMP请求包,按住键盘上的control + c停止发送,如图。

image.jpg

通过wireshark抓取到的ICMP请求包如图,其中type字段数据为8h08code字段数据为8h00,表示主动请求。

image.jpg

FPGA接收到PC发送的ICMP请求包,会回复一个ICMP应答包,通过wireshark抓取到的ICMP应答包如图所示,其中type字段数据为8h00code字段数据为8h00,表示回显应答。

image.jpg

UDPICMP数据包都要经过IP层进行发送,为验证它们在发送时是否冲突,我们在操作PC在发送ICMP请求的同时发送UDP数据包给FPGAwireshark可以抓取到ICMP包和UDP包,在netassist界面中,接收和发送的UDP包数量和有效数据数量相等,说明由于在发送端使用了FIFO缓存数据,二者在发送时并无冲突。

image.jpg
5.4 FPGA主动发送验证

修改顶层文件,每隔一定的时间,FPGA主动发送一帧数据给PCFPGA会先发送ARP广播给PCPC收到广播后,将自己的MAC地址通过ARP应答包发送给FPGAFPGAPCMAC地址存入cache中,开始对PC发送数据帧。PC端通过科来数据包生成器发送PAUSE帧给FPGA,以实现流量控制效果。

5.4.1 FPGA发送ARP主动请求

将程序下载进FPGA中,FPGA会发送ARP广播包给PCwireshark抓取的ARP广播内容如图所示。

image.jpg

PC收到广播后,将自己的MAC地址打包成ARP应答包发送给FPGAwireshark抓取的ARP应答包的内容如图所示。之后FPGA开始发送数据帧到PC

image.jpg

5.4.2 通过PAUSE帧控制流量

通过科来数据包生成器组PAUSE帧,以固定间隔发送给FPGAPAUSE帧的内容如图所示。

image.jpg
image.jpg

通过ILA抓取FPGA中的暂停标志信号pause_flag,可以看到pause_flag成功拉高一段时间。\

image.jpg

米联客UDP协议栈的所有功能验证完毕

















您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

0

关注

0

粉丝

277

主题
精彩推荐
热门资讯
网友晒图
图文推荐

  • 微信公众平台

  • 扫描访问手机版