FPGA以太网入门(二)——ARP测试实验(基于紫光同创,含原语介绍)

FPGA以太网入门(二)——ARP测试实验(基于紫光同创,含原语介绍)

0 致读者

此篇为专栏《紫光同创FPGA开发笔记》第六篇,同时也是 FPGA 以太网入门第二篇,记录我的学习 FPGA 的一些开发过程和心得感悟,刚接触 FPGA 的朋友们可以先去此博客 《FPGA零基础入门学习路线》来做最基础的扫盲。

本篇内容基于笔者实际开发过程和正点原子资料撰写,将会详细讲解此 FPGA 实验的全流程,诚挚地欢迎各位读者在评论区或者私信我交流!

在以太网中,一个主机和另一个主机进行通信,必须要知道目的主机的 MAC 地址(物理地址),而目的 MAC 地址的获取由 ARP 协议完成。本文我们来学习如何通过 FPGA 开发板实现 ARP 协议的功能。

本文的工程文件开源地址如下(基于ATK-DFPGL22G,大家 clone 到本地就可以直接跑仿真,如果要上板请根据自己的开发板更改约束即可):

https://github.com/ChinaRyan666/PDS_ARP_TEST



1 实验任务

本节实验任务是使用 ATK-DFPGL22G 开发板上的以太网接口,和上位机实现 ARP 请求和应答的功能。当上位机发送 ARP 请求时,开发板返回 ARP 应答数据。当按下开发板的触摸按键时,开发板发送 ARP 请求,此时上位机返回应答数据。



2 简介

2.1 ARP 概述

ARPAddress Resolution Protocol),即地址解析协议,是根据 IP 地址(逻辑地址)获取 MAC 地址的一种 TCP/IP 协议。在以太网通信中,数据是以 “帧” 的格式进行传输的,帧格式里面包含目的主机的 MAC 地址。源主机的应用程序知道目的主机的 IP 地址,却不知道目的主机的 MAC 地址。而目的主机的 MAC 地址直接被网卡接收和解析,当解析到目的 MAC 地址非本地 MAC 地址时,则直接丢弃该包数据,因此在通信前需要先获得目的的 MAC 地址,而 ARP 协议正是实现了此功能。

ARP 协议的基本功能是通过目的设备的 IP 地址,查询目的设备的 MAC 地址,以保证通信的顺利进行。MAC 地址在网络中表示网卡的 ID,每个网卡都需要并有且仅有一个 MAC 地址。在获取到目的 MAC 地址之后,将目的 MAC 地址更新至 ARP 缓存表中,称为 ARP 映射,下次通信时,可以直接从 ARP 缓存表中获取,而不用重新通过 ARP 获取 MAC 地址。但一般 ARP 缓存表会有过期时间,过期后需要重新通过 ARP 协议进行获取。

ARP 映射是指将 IP 地址和 MAC 地址映射起来,分为静态映射和动态映射。

静态映射指手动创建一张 ARP 表,把 IP 地址和 MAC 地址关联起来。手动绑定之后,源主机在通信之前,就可以直接从 ARP 表中直接找到 IP 地址对应的 MAC 地址,但这样做有一定的局限性,因为 MAC 地址可能会变化,比如:

  1. 机器可能更换 NIC(网络适配器),结果变成一个新的物理地址;

  2. 在某些局域网中,每当计算机加电时,他的物理地址都要改变一次;

  3. 移动电脑可以从一个物理网络转移到另一个物理网络,这样会改变物理地址。

要避免这些问题出现,必须定期维护更新 ARP 表,此类比较麻烦而且会影响网络性能。动态映射指使用协议来获取相对应的物理地址,之所以用动态这个词是因为这个过程是自动完成的,一般应用程序的用户或系统管理员不必关心。

已经设计出用于实现动态映射协议的有 ARPRARP(逆地址解析协议)两种,如下图所示。

在这里插入图片描述

ARPIP 地址映射为物理地址,RARP 把物理地址映射为 IP 地址。RRAP 是被那些没有磁盘驱动器的系统使用(一般是无盘工作站或 X 终端),此类应用较少,本章不做讨论。

ARP 协议分为 ARP 请求和 ARP 应答,源主机发起查询目的 MAC 地址的报文称为 ARP 请求,目的主机响应源主机并发送包含本地 MAC 地址的报文称为 ARP 应答。

当主机需要找出这个网络中的另一个主机的物理地址时,它就可以发送一个 ARP 请求报文,这个报文包含了发送方的 MAC 地址和 IP 地址以及接收方的 IP 地址。因为发送方不知道接收方的物理地址,所以这个查询分组会在网络层中进行广播,即 ARP 请求时发送的接收方物理地址为广播地址,用 48’hff_ff_ff_ff_ff_ff 表示。

ARP 请求的示意图如下图所示。

在这里插入图片描述

上图中的主机 A 发起 ARP 请求,由于发送的目的 MAC 地址为广播地址,所以此时局域网中的所有主机都会进行接收并处理这个 ARP 请求报文,然后进行验证,查看接收方的 IP 地址是不是自己的地址。是则返回 ARP 应答报文,不是则不响应。

只有验证成功的主机才会返回一个 ARP 应答报文,这个应答报文包含接收方的 IP 地址和物理地址。

ARP 应答的示意图如下图所示。

在这里插入图片描述

主机 B 利用收到的 ARP 请求报文中的请求方物理地址,以单播的方式直接发送给主机 A,主机 A 将收到的 ARP 应答报文中的目的 MAC 地址解析出来,将目的 MAC 地址和目的 IP 地址更新至 ARP 缓存表中。当再次和主机 A 通信时,可以直接从 ARP 缓存表中获取,而不用重新发起 ARP 请求报文。需要说明的是,ARP 缓存表中的表项有过期时间(一般为 20 分钟),过期之后,需要重新发起 ARP 请求以获取目的 MAC 地址。

ARP 协议通过以太网进行传输,那么必须也要按照以太网所规定的格式进行传输,我们先来介绍下以太网的帧格式,随后再来向大家详细介绍 ARP 协议的具体格式。

以太网是目前应用最广泛的局域网通讯方式,同时也是一种协议。以太网协议定义了一系列软件和硬件标准,从而将不同的计算机设备连接在一起。我们知道串口通信单次只传输一个字节,而以太网通信是以数据包的形式传输,其单包数据量达到几十,甚至成百上千个字节。下图为以太网通过 ARP 传输单包数据的格式,从图中可以看出,以太网的数据包就是对协议的封装来实现数据的传输,即 ARP 数据位于以太网帧格式的数据段。这里只是让大家了解下以太网数据包的格式,后面会逐个展开来讲。

在这里插入图片描述


2.2 以太网 MAC 帧格式

以太网技术的正式标准是 IEEE 802.3,它规定了以太网传输数据的帧结构,我们可以把以太网 MAC 层理解成高速公路,我们必须遵循它的规则才能在上面通行,以太网 MAC 层帧格式如图下图所示。

在这里插入图片描述

以太网传输数据时按照上面的顺序从头到尾依次被发送和接收,我下面进一步解释各个区域。

名称功能
前导码(Preamble为了实现底层数据的正确阐述,物理层使用 7 个字节同步码【01 交替(55-55-55-55-55-55-55)】实现数据的同步。
帧起始界定符(SFD,Start Frame Delimiter使用 1 个字节的 SFD(固定值为 0xd5)来表示一帧的开始,即后面紧跟着传输的就是以太网的帧头。
目的 MAC 地址即接收端物理 MAC 地址,占用 6 个字节。MAC 地址从应用上可分为单播地址、组播地址和广播地址。单播地址: 第一个字节的最低位为 0,比如 00-00-00-11-11-11,一般用于标志唯一的设备;组播地址: 第一个字节的最低位为 1,比如 01-00-00-11-11-11,一般用于标志同属一组的多个设备;广播地址: 所有 48bit 全为 1,即 FF-FF-FF-FF-FF-FF,它用于标志同一网段中的所有设备。
MAC 地址即发送端物理 MAC 地址,占用 6 个字节。
长度/类型上图中的长度/类型具有两个意义,当这两个字节的值小于 1536(十六进制为 0x0600)时,代表该以太网中数据段的长度;如果这两个字节的值大于 1536,则表示该以太网中的数据属于哪个上层协议,例如 0x0800 代表 IP 协议(网际协议)、0x0806 代表 ARP 协议(地址解析协议)等。
数据以太网中的数据段长度最小 46 个字节, 最大 1500 个字节。最大值 1500 称为以太网的最大传输单元(MTU, Maximum Transmission Unit),之所以限制最大传输单元是因为在多个计算机的数据帧排队等待传输时,如果某个数据帧太大的话,那么其它数据帧等待的时间就会加长,导致体验变差,这就像一个十字路口的红绿灯,你可以让绿灯持续亮一小时,但是等红灯的人一定不愿意的。另外还要考虑网络 I/O 控制器缓存区资源以及网络最大的承载能力等因素,因此最大传输单元是由各种综合因素决定的。为了避免增加额外的配置,通常以太网的有效数据字段小于 1500 个字节。
帧检验序列(FCS, Frame Check Sequence为了确保数据的正确传输,在数据的尾部加入了 4 个字节的循环冗余校验码(CRC 校验)来检测数据是否传输错误。CRC 数据校验从以太网帧头开始即不包含前导码和帧起始界定符。通用的 CRC 标准有 CRC-8CRC-16CRC-32CRC-CCIT,其中在网络通信系统中应用最广泛的是 CRC-32 标准。

在这里还有一个要注意的地方就是以太网相邻两帧之间的时间间隔,即帧间隙(IFG, Interpacket Gap)。

帧间隙的时间就是网络设备和组件在接收一帧之后,需要短暂的时间来恢复并为接收下一帧做准备的时间,IFG 的最小值是 96 bit time,即在媒介中发送 96 位原始数据所需要的时间,在不同媒介中 IFG 的最小值是不一样的。不管 10M/100M/1000M 的以太网,两帧之间最少要有 96bit timeIFG 的最少间隔。

时间计算方法如下:

10Mbit/s 最小时间为:96*100ns = 9600ns
100Mbit/s 最小时间为:96*10ns = 960ns
1000Mbit/s 最小时间为:96*1ns = 96ns

接下来我来介绍 ARP 协议以及它和以太网 MAC 层的关系。在介绍 ARP 协议之前,我们先了解下 TCP(传输控制协议)/ IP(网际协议)协议簇。TCP/IP 是网络使用中最基本的通信协议,虽然从名字看上去 TCP/IP 包括两个协议,TCPIP,但 TCP/IP 实际上是一组协议,它包括上百个各种功能的协议,如:TCPIPARPUDP 等。 而 TCP 协议和 IP 协议是保证数据完整传输的两个重要的协议,因此 TCP/IP 协议用来表示 Internet 协议簇。

TCP/IP 协议不仅可以运行在以太网上,也可以运行在 FDDI(光纤分布式数据接口)和 WLAN(无线局域网)上。反过来,以太网的高层协议不仅可以是 TCP/IP 协议,也可以是 IPX 协议(互联网分组交换协议)等,只不过以太网 + TCP/IP 成为 IT 行业中应用最普遍的技术。下面我们来熟悉下 ARP 协议。


2.3 ARP 协议

ARP 协议属于 TCP/IP 协议簇的一种,从前面介绍的图(以太网ARP 数据包格式)可以看出,ARP 协议位于以太网 MAC 帧格式的数据段,ARP 数据包格式如下图所示。

在这里插入图片描述

名称功能
硬件类型(Hardware type硬件地址的类型,1 表示以太网地址。
协议类型(Protocol type要映射的协议地址类型,ARP 协议的上层协议为 IP 协议,因此该协议类型为 IP 协议,其值为 0x0800
硬件地址长度(Hardware size硬件地址(MAC 地址)的长度,以字节为单位。对于以太网上 IP 地址的 ARP 请求或者应答来说,该值为 6
协议地址长度(Protocol sizeIP 地址的长度,以字节为单位。对于以太网上 IP 地址的 ARP 请求或者应答来说,该值为 4
OPOpcode操作码,用于表示该数据包为 ARP 请求或者 ARP 应答。1 表示 ARP 请求,2 表示 ARP 应答。
MAC 地址发送端的硬件地址。
IP 地址发送端的协议(IP)地址,如 192.168.1.102
目的 MAC 地址接收端的硬件地址,在 ARP 请求时由于不知道接收端 MAC 地址,因此该字段为广播地址, 即 48’hff_ff_ff_ff_ff_ff
目的 IP 地址接收端的协议(IP)地址,如 192.168.1.10

以太网的帧格式、ARP 数据格式到这里已经全部介绍完了,关于通过以太网传输 ARP 报文的格式如下图所示。

在这里插入图片描述

由上图可知,28 字节的 ARP 数据位于以太网帧格式的数据段。由于以太网数据段最少为 46 个字节,而 ARP 数据包总长度为 28 个字节,因此在 ARP 数据段后面需要填充 18 个字节的数据,以满足以太网传输格式的要求。这个填充的过程称为 Padding(填充),填充的数据可以为任意值,但一般为 0


2.4 RGMII 接口介绍

以太网的通信离不开物理层 PHY 芯片的支持,以太网 MACPHY 之间有一个接口,常用的接口有 MIIRMIIGMIIRGMII 等。

名称功能
MIIMedium Independent Interface,媒体独立接口)MII 支持 10Mbps100Mbps 的操作,数据位宽为 4 位,在 100Mbps 传输速率下,时钟频率为 25Mhz
RMIIReduced MIIRMIIMII 的简化版,数据位宽为 2 位,在 100Mbps 传输速率下,时钟频率为 50Mhz
GMIIGigabit MIIGMII 接口向下兼容 MII 接口,支持 10Mbps100Mbps1000Mbps 的操作,数据位宽为 8 位,在 1000Mbps 传输速率下,时钟频率为 125Mhz
RGMIIReduced GMIIRGMIIGMII 的简化版,数据位宽为 4 位,在 1000Mbps 传输速率下,时钟频率为 125Mhz,在时钟的上下沿同时采样数据。在 100Mbps10Mbps 通信速率下,为单个时钟沿采样。

在千兆以太网中,常用的接口为 RGMIIGMII 接口。RGMII 接口的优势是同时适用于 10M/100M/1000Mbps 通信速率,同时占用的引脚数较少。但 RGMII 接口也有其缺点,就是在 PCB 布线时需要尽可能对时钟、控制和数据线进行等长处理,且时序约束相对也更为严格。

为了节省引脚,目前市面上大部分 FPGA 开发板板载的 PHY 芯片采用的接口为 RGMII 接口,下图是 MAC 侧与 PHY 侧接口的连接。

在这里插入图片描述

名称功能
ETH_RXC接收数据参考时钟,1000Mbps 速率下,时钟频率为 125MHz,时钟为上下沿同时采样;100Mbps 速率下,时钟频率为 25MHz10Mbps 速率下,时钟频率为 2.5MHzETH_RXCPHY 侧提供。
ETH_RXCTLETH_RX_DV接收数据控制信号。
ETH_RXD四位并行的接收数据线。
ETH_TXC发送参考时钟,1000Mbps 速率下,时钟频率为 125MHz,时钟为上下沿同时采样;100Mbps 速率下,时钟频率为 25MHz10Mbps 速率下,时钟频率为 2.5MHzETH_TXCMAC 侧提供。
ETH_TXCTLETH_TXEN发送数据控制信号。
ETH_TXD四位并行的发送数据线。
ETH_RESET_N芯片复位信号,低电平有效。
ETH_MDC数据管理时钟(Management Data Clock),该引脚对 ETH_MDIO 信号提供了一个同步的时钟。
ETH_MDIO数据输入/输出管理(Management Data Input/Output),该引脚提供了一个双向信号用于传递管理信息。

其中 ETH_RXCETH_RXCTLETH_RXDMAC 接收侧引脚;ETH_TXCETH_TXCTLETH_TXDMAC 发送侧引脚;ETH_MDCETH_MDIOMDIO 接口引脚,用于配置 PHY 芯片内部寄存器;ETH_RST_NPHY 芯片硬件复位信号。

由于 PHY 芯片的内部寄存器在默认配置下也可以正常工作,因此本次实验没有对 MDIO 接口进行读写操作,只用到了以太网的 RGMII 接口信号和复位信号。

RGMII 使用 4bit 数据接口,在 1000Mbps 通信速率下,ETH_TXCETH_RXC 的时钟频率为 125Mhz,采用上下沿 DDRDouble Data Rate)的方式在一个时钟周期内传输 8 位数据信号,即上升沿发送/接收低 4 位数据,下降沿发送/接收高 4 位数据。

ETH_TXCTLETH_RXCTL 控制信号同样采用 DDR 的方式在一个时钟周期内传输两位控制信号,即上升沿发送/接收数据使能(TX_EN/RX_DV)信号,下降沿发送 / 接收使能信号与错误信号的异或值(TX_ERR xor TX_ENRX_ERR xor RX_DV)。

RX_DV 为高电平(表示数据有效),RX_ERR 为低电平(表示数据无错误),则异或的结果值为高电平,因此只有当 ETH_RXCTLETH_TXCTL 信号的上下沿同时为高电平时,发送和接收的数据有效且正确。

RGMII 工作在 100Mbps 时,ETH_TXCETH_RXC 的时钟频率为 25Mhz,采用上升沿 SDR 的方式在一个周期内传输 4 位数据。不过此时 ETH_TXCTLETH_RXCTL 控制信号仍采用上下沿 DDR 的传输方式。

RGMII 工作在 10Mbps 时,ETH_TXCETH_RXC 的时钟频率为 2.5Mhz,采用上升沿 SDR 的方式在一个周期内传输 4 位数据。ETH_TXCTLETH_RXCTL 控制信号也采用 SDR 的传输方式。


2.5 RGMII 接口时序

PHY 芯片的 RGMII 接口时序,其时钟、控制信号和数据的对齐方式,一般由 MDIO 接口或者硬件上的特殊引脚进行配置。

RGMII 接收端口时钟、控制信号和数据对齐的时序图如下所示。

在这里插入图片描述

由上图可知,RXC 的上下边沿与 RXDRX_CTL 信号对齐,相位相同。

RGMII 接收端口时钟和控制 / 数据信号增加延时的时序图如下图所示。

在这里插入图片描述

由上图可知,RXC 的上下边沿与 RXDRX_CTL 信号的中间位置对齐,RXC 的时钟周期为 8ns,单个高电平或者低电平为 4nsRXC 相对于 RXDRX_CTL 延时约 2ns

YT8511 RGMII 接收端口的信号对齐模式由硬件上的特殊引脚外接上下拉电阻进行配置,如下图所示。

在这里插入图片描述

从上图中可以看出,当管脚 LED_10_100 接上拉电阻时,表示 RXC 时钟相对于 RXD 信号,会增加约 2ns 的延时。

我做本次实验使用的ATK-DFPGL22G 硬件原理图中 YT8511 的管脚 LED_10_100 连接的是上拉电阻,因此 RXCRXD 之间会有约 2ns 的延时。

RGMII 发送端口正常模式时序图如下所示。

在这里插入图片描述

由上图可知,RGMII 发送端口正常模式下,需要满足 TXC 的上下边沿与 TXDTX_CTL 信号对齐,相位相同。YT8511 在硬件上面没有做 TX 端的 delay 模式,可根据实际情况,选择是否在代码中进行延时(因为一般对端设备的接收端会有延时处理的功能,因此发送端也可以不延时),延时后的时序图如下图所示。

在这里插入图片描述

RGMII 的接口时序可知,RGMII 发送端口在 TXC 时钟的上升沿传输 TXD 的低 4 位和 TX_CTL 的使能信号;下降沿传输 TXD 的高 4 位和 TX_CTL 的错误信号(实际上是使能信号和错误信号的异或值);RGMII 接收端口在 RXC 时钟的上升沿传输 RXD 的低 4 位和 RX_CTL 的使能信号;下降沿传输 RXD 的高 4 位和 RX_CTL 的错误信号(实际上是使能信号和错误信号的异或值)。

至此,关于本实验的理论知识讲解完毕。为了更高效的完成设计,我用到了原语,这是本频道的公开实验中第一次用到原语,下面我会给大家详细的进行讲解。



3 Pango 原语

原语,即 primitive,原语类似最底层的描述方法,是不同厂商针对自己 FPGA 芯片提供的底逻辑资源的描述。因此不同的厂商,原语不同,同一家的 FPGA,由于不同系列的芯片内部资源一般不同,原语也是不通用的。使用原语的好处,可以直接例化使用,不用定制 IP。我们在进行 FPGA 开发时的 HDL 代码在进行综合后的输出就是由原语组成的逻辑网表,因此原语是不会参于综合过程的。

我们在集成开发工具中能够例化的原语模板实质不是真正的原语,为了设计人员设计方便其已经经过一层封装,不过通常还是将其称为原语。正如前面所说,原语是对底层资源的直接描述,因此其抽象层次低,是需要考虑实现细节更多的一种实现,采用原语的设计方式对于 FPGA 底层资源理解要高。一般在实际 HDL 代码编写中,不需要去进行原语的调用,我们进行 HDL 代码这种抽象层次更高的设计,将具体细节交给了集成开发环境完成。

本文中讲解的原语基于紫光同创,是 Pango 器件底层硬件中的功能模块,它使用专用的资源来实现一系列的功能。相比于 IP 核,原语的调用方法更简单,但是一般只用于实现一些简单的功能。本文主要用到了 GTP_OUTBUFTIDDRODDR,接下来给大家进行详细的讲解。


3.1 GTP_OUTBUFT 使用说明

1) 功能介绍

GTP_OUTBUFT: 是一个三态输出 BUFFER,如下图所示。

在这里插入图片描述

2) 端口描述

在这里插入图片描述

3) 参数描述

在这里插入图片描述

4) 实例模板

GTP_OUTBUFT #(
.IOSTANDARD 	("DEFAULT"),
.SLEW_RATE 		("SLOW "),
.DRIVE_STRENGTH (8)
)
GTP_OUTBUFT_inst (
.I (i),
.O (o),
.T (t)
);

3.2 GTP_OSERDES 使用说明

Output DDR 的主要功能是把来自 Fabric 的数据,从 CLK_SYS 转移到 SERCLK 时钟域,并转换成高速的串行数据流发送出去。每个 Output DDR 单元可支持输出速率转换 2:14:17:18:1

Output DDR 单元可通过参数 OSERDES_MODE 配置成不同的工作模式,这些工作模式包括:ODDROMDDROSER4OMSER4OSER7OSER8OMSER8PDS 软件库为方便用户使用 Output DDR 单元提供了专用原语,用户可以在源代码(Verilog/VHDL)中例化 GTP_OSERDES 原型模块。以 Verilog 例化为例:

GTP_OSERDES #(
.OSERDES_MODE (“ODDR” ),
.WL_EXTEND 	  (“FALSE” ),
.GRS_EN 	  (“TRUE” ),
.LRS_EN 	  (“TRUE” ),
.TSDDR_INIT   (1'b0 )
)
u_OSERDES(
.DO 	(DO ),
.TQ 	(TQ ),
.DI 	(DI ),
.TI 	(TI ),
.RCLK   (RCLK ),
.SERCLK (SERCLK ),
.OCLK   (OCLK ),
.RST    (RST )
);	

GTP_OSERDES 的参数及信号说明如下:

在这里插入图片描述
GTP_OSERDES 通常跟 GTP_OUTBUFGTP_OUTBUFDSGTP_OUTBUFCOGTP_OUTBUFTCOGTP_OUTBUFTDSGTP_OUTBUFT 一起使用。下图以 GTP_OUTBUFT 为例,说明了 GTP_OSERDES 与之连接的关系,GTP_OUTBUFT 的详细说明如下图所示。

在这里插入图片描述
在使用 GTP_OSERDES 时,分为有三态控制和没有三态控制两类模式。当没有三态控制时,GTP_OSERDES 是不开放 TITQ 的。

下面分别介绍 Output DDR 不同的工作模式。


3.2.1 ODDR

Output DDR 配置为 ODDR 模式时,其功能图可化简为下图。

在这里插入图片描述

ODDR 时序图如下所示。

在这里插入图片描述

ODDROMDDROSER4OMSER4OSER8OMSER8 均有两种形态,一种为如上图所示的带三态控制 TS_TO 的输出,另一种为不带三态输出的输出,OSER7 只有不带 TS_TO 的输出。


3.2.2 OMDDR

Output DDR 配置为 OMDDR 模式时,其功能图可化简为下图。

在这里插入图片描述

ODDR 相比,OMDDR 多一次从 CLK_SYSOCLK 的时钟域转换,时序图如下图所示。

在这里插入图片描述

3.2.3 OSER4

Output DDR 配置为 OSER4 模式时,与 GTP_OUTBUFTGTP_OUTBUFTDSGTP_OUTBUFTCO 配合使用。其功能图可化简为下图。

在这里插入图片描述

OSER4 时序图如下图所示。

在这里插入图片描述

3.2.4 OMSER4

Output DDR 配置为 OMSER4 模式时,其功能图可化简为下图。

在这里插入图片描述

OSER4 相比,OMSER4 增加了从 SERCLKOCLK 的时钟域转换。OMSER4 的时序图如下图所示。

在这里插入图片描述


3.2.5 OSER7

Output DDR 配置为 OSER7 模式时,与 GTP_OUTBUFGTP_OUTBUFDSGTP_OUTBUFCO 配合使用。其功能图可化简为下图。

在这里插入图片描述

OSER7 时序图如下图所示。

在这里插入图片描述

3.2.6 OSER8

Output DDR 配置为 OSER8 模式时,使用方法与 OSER4 相同。其功能图可化简为下图。

在这里插入图片描述

OSER8 时序图如下图所示。

在这里插入图片描述

3.2.7 OMSER8

Output DDR 配置为 OMSER8 模式时,其功能图可化简为下图。

在这里插入图片描述

这里的 CLK_R 选择是 DQSL 接口时钟相移 180 度。

OSER8 相比,OMSER8 增加了从 SERCLKOCLK 的时钟域转换。

OMSER8 时序图如下图所示。

在这里插入图片描述

3.3 GTP_ISERDES 使用说明

Input DDR 单元主要由 IFIFOGEAR LOGIC 两部分组成。其中,IFIFO 主要作用于 DDR memory interface 中,其功能包括从外部非连续 DQS 到内部连续时钟的时钟域转换,DDR3 读数据的 realign,一些特殊的 Generic DDR 应用以及补偿采样时钟和内部时钟的相位差;GEAR LOGIC 的主要功能是扩展采样后的数据的位宽,并把它从高速的 DESCLK 转移到较低速的系统时钟域,以方便 Fabric 处理。

Input DDR 单元可通过参数 ISERDES_MODE 配置成不同的工作模式,这些工作模式包括:IDDRIMDDRIDES4IMDES4IDES7IDES8IMDES8PDS 软件库为方便用户使用 Input DDR 单元提供了专用原语,用户可以在源代码(Verilog/VHDL)中例化 GTP_ISERDES 原型模块。以 Verilog 例化为例:

GTP_ISERDES #(
.ISERDES_MODE	(“IDDR” ),
.GRS_EN			(“TRUE” ),
.LRS_EN			(“TRUE” )
) 
u_ISERDES(
.DI 	(DI ),
.ICLK 	(ICLK ),
.DESCLK (DESCLK ),
.RCLK	(RCLK ),
.WADDR 	(WADDR ),
.RADDR 	(RADDR ),
.RST 	(RST ),
.DO 	(DO )
);

GTP_ISERDES 的参数及信号说明如下:

在这里插入图片描述
GTP_ISERDES 通常会与 GTP_INBUFGTP_INBUFGGTP_INBUFDSGTP_INBUFGDS 一起使用,下图以 GTP_INBUFDS 为例说明 GTP_ISERDES 与之连接的方式。

在这里插入图片描述
下面分别介绍 Input DDR 的不同工作模式。


3.3.1 IDDR

Input DDR 配置为 IDDR 模式时,其功能图可化简为下图。

在这里插入图片描述
IDDR 时序图如下图所示:

在这里插入图片描述

注:时序图中的 abcd 等字母用以代表 bit 数据的位置信息, 就数据而言数值为 ‘0’‘1’


3.3.2 IMDDR

Input DDR 配置为 IMDDR 模式时,与 IDDR 结构相比,IMDDR 使能了其中的 IFIFO。其功能图可简化为下面的图例。

在这里插入图片描述

IMDDR 时序图如下图所示。

在这里插入图片描述


3.3.3 IDES4

Input DDR 配置为 IDES4 时,与 GTP_INBUFGTP_INBUFGGTP_INBUFDSGTP_INBUFGDS 配合使用。其功能图可简化为下面的图例。

在这里插入图片描述

IDES4 时序图如下图所示。

在这里插入图片描述


3.3.4 IMDES4

Input DDR 配置为 IMDES4 时,与 IDES4 相比,IMDES4 使能了其中的 IFIFO。其功能图可简化为如下图例。

在这里插入图片描述

IMDES4 时序图如下图所示。

在这里插入图片描述

upd 信号在异步 RESET 之后的 1clk 周期后,开始起作用,并且每两个 CLK_IO 有效一次。


3.3.5 IDES7

Input DDR 配置为 IDES7 时,使用方法与 IDES4 相同。其功能图可简化为如下图例。

在这里插入图片描述

IDES7 时序图如下图所示。

在这里插入图片描述

3.3.6 IDES8

Input DDR 配置为 IDES8 时,使用方法与 IDES4 相同。其功能图可简化为如下图例。

在这里插入图片描述

IDES8 时序图如下图所示。

在这里插入图片描述

3.3.7 IMDES8

Input DDR 配置为 IMDES8 时,与 IDES8 相比,IMDES8 使能了其中的 IFIFO。其功能图可简化为如下图例。

在这里插入图片描述

IMDES8 时序图如下图所示。

在这里插入图片描述

IMDES8 功能时,upd 信号在异步 rst 之后的 2clk 周期后,开始起作用,并且每 4clk 周期有效一次。



3 程序设计

3.1 总体设计

学习了以上的基本概念,接下来就可以开始程序的设计了,我们先回顾下实验任务,如下图所示。

在这里插入图片描述

根据实验任务,我们可以大致规划出系统的控制流程:首先我们需要完成 RGMII 接口数据和 GMII 接口数据的转换,以方便数据的采集和解析,在数据采集过程中所用到的延时原语参考时钟由锁相环输出的时钟提供;其次整个以太网帧格式与 ARP 协议的实现由 ARP 顶层模块完成;ARP 控制模块负责检测输入的触摸按键是否被按下,控制 ARP 顶层模块发起请求与产生应答等操作。

由此画出系统的功能框图如下图所示:

在这里插入图片描述

GMII TO RGMII 模块负责将双沿(DDR)数据和单沿(SDR)数据之间的转换;ARP 顶层模块实现了以太网 ARP 数据包的接收、发送以及 CRC 校验的功能;ARP 控制模块根据输入的按键触摸信号和接收到的 ARP 请求信号,控制 ARP 顶层模块发送 ARP 请求或者 ARP 应答。

FPGA 顶层模块例化了以下四个模块, 网口复位模块(reset_dly)、GMII TO RGMII 模块(gmii_to_rgmii)、ARP 顶层模块(arp)和 ARP 控制模块(arp_ctrl),实现了各模块之间的数据交互。

其中 ARP 顶层模块和 GMII TO RGMII 模块内部也例化了多个其它模块,这样设计的目的是为了方便模块的重用。

顶层模块的代码如下:

module eth_arp_test(
    input              sys_clk   , //系统时钟
    input              sys_rst_n , //系统复位信号,低电平有效 
    input              touch_key , //触摸按键,用于触发开发板发出ARP请求
    //PL以太网RGMII接口   
    input              eth_rxc   , //RGMII接收数据时钟
    input              eth_rx_ctl, //RGMII输入数据有效信号
    input       [3:0]  eth_rxd   , //RGMII输入数据
    output             eth_txc   , //RGMII发送数据时钟    
    output             eth_tx_ctl, //RGMII输出数据有效信号
    output      [3:0]  eth_txd   , //RGMII输出数据          
    output             eth_rst_n   //以太网芯片复位信号,低电平有效   
    );

//parameter define
//开发板MAC地址 00-11-22-33-44-55
parameter  BOARD_MAC = 48'h00_11_22_33_44_55;
//开发板IP地址 192.168.1.10     
parameter  BOARD_IP  = {8'd192,8'd168,8'd1,8'd10};
//目的MAC地址 ff_ff_ff_ff_ff_ff
parameter  DES_MAC   = 48'hff_ff_ff_ff_ff_ff;
//目的IP地址 192.168.1.102
parameter  DES_IP    = {8'd192,8'd168,8'd1,8'd102};

//wire define
wire  [7:0]   gmii_txd    ; //GMII发送数据
wire          gmii_tx_en  ; //GMII发送数据使能信号
wire          gmii_tx_clk ; //GMII发送时钟
wire  [7:0]   gmii_rxd    ; //GMII接收数据 
wire          gmii_rx_dv  ; //GMII接收数据有效信号
wire          gmii_rx_clk ; //GMII接收时钟

wire          arp_rx_done ; //ARP接收完成信号
wire          arp_rx_type ; //ARP接收类型 0:请求  1:应答
wire  [47:0]  src_mac     ; //接收到目的MAC地址
wire  [31:0]  src_ip      ; //接收到目的IP地址    
wire          arp_tx_en   ; //ARP发送使能信号
wire          arp_tx_type ; //ARP发送类型 0:请求  1:应答
wire          tx_done     ; //ARP发送完成信号 
wire  [47:0]  des_mac     ; //发送的目标IP地址
wire  [31:0]  des_ip      ; //以太网发送完成信号
//*****************************************************
//**                    main code
//*****************************************************
assign des_mac = src_mac;
assign des_ip = src_ip;

//输出网口复位信号
reset_dly delay_u0(
    .clk           (sys_clk  ),
    .rst_n         (sys_rst_n),
    .rst_n_dly     (eth_rst_n)
);

gmii_to_rgmii u_gmii_to_rgmii(
	.rgmii_txd              (eth_txd   ),
	.rgmii_tx_ctl           (eth_tx_ctl),
	.rgmii_txc              (eth_txc   ),
	.rgmii_rxd              (eth_rxd   ),
	.rgmii_rx_ctl           (eth_rx_ctl),
    .rgmii_rxc              (eth_rxc   ),
    
	.gmii_rx_clk            (gmii_rx_clk),
	.gmii_txd               (gmii_txd   ),
	.gmii_tx_en             (gmii_tx_en ),
	.gmii_tx_clk            (gmii_tx_clk),
	.gmii_rxd               (gmii_rxd   ),
	.gmii_rx_dv             (gmii_rx_dv )
	);

//ARP通信
arp                                             
   #(
    .BOARD_MAC     (BOARD_MAC),      //参数例化
    .BOARD_IP      (BOARD_IP ),
    .DES_MAC       (DES_MAC  ),
    .DES_IP        (DES_IP   )
    )
   u_arp(
    .rst_n         (sys_rst_n  ),
                    
    .gmii_rx_clk   (gmii_rx_clk),
    .gmii_rx_dv    (gmii_rx_dv ),
    .gmii_rxd      (gmii_rxd   ),
    .gmii_tx_clk   (gmii_tx_clk),
    .gmii_tx_en    (gmii_tx_en ),
    .gmii_txd      (gmii_txd   ),
                    
    .arp_rx_done   (arp_rx_done),
    .arp_rx_type   (arp_rx_type),
    .src_mac       (src_mac    ),
    .src_ip        (src_ip     ),
    .arp_tx_en     (arp_tx_en  ),
    .arp_tx_type   (arp_tx_type),
    .des_mac       (des_mac    ),
    .des_ip        (des_ip     ),
    .tx_done       (tx_done    )
    );

//ARP控制
arp_ctrl u_arp_ctrl(
    .clk           (gmii_rx_clk),
    .rst_n         (sys_rst_n  ),
 
    .touch_key     (touch_key  ),
    .arp_rx_done   (arp_rx_done),
    .arp_rx_type   (arp_rx_type),
    .arp_tx_en     (arp_tx_en  ),
    .arp_tx_type   (arp_tx_type)
    );

endmodule

顶层模块主要完成对其余模块的例化。在程序的第 16 行至第 23 行代码定义了开发板的 MAC 地址、IP 地址、默认的目的 MAC 地址和目的 IP 地址。

开发板的 MAC 地址为 00:11:22:33:44:55;可开发板的 IP 地址为 192.168.1.10;默认目的 MAC 地址为 ff:ff:ff:ff:ff:ff,这是一个广播 MAC 地址,在收到上位机的请求或者应答之后,ARP 模块会替换成实际的目的 MAC 地址。目的 IP 地址这里设置为 192.168.1.102,因此大家在做本次实验时,需要把电脑的以太网的 IP 地址改成 192.168.1.102,或者将代码中定义的 DES_IP 改成电脑的 IP 地址。

这部分非常重要!否则上板实验无法成功,关于如何修改 IP 见本文第四部分(下载验证)。

程序的第 45 行和 46 行代码将收到的对端设备 MAC 地址和目的 IP 地址,作为开发板发送时的目的 MAC 地址和 IP 地址。


3.2 gmii_to_rgmii 模块设计

gmii_to_rgmii 模块代码如下:

module gmii_to_rgmii(
    //以太网GMII接口
    output             gmii_rx_clk , //GMII接收时钟
    output             gmii_rx_dv  , //GMII接收数据有效信号
    output      [7:0]  gmii_rxd    , //GMII接收数据
    output             gmii_tx_clk , //GMII发送时钟
    input              gmii_tx_en  , //GMII发送数据使能信号
    input       [7:0]  gmii_txd    , //GMII发送数据            
    //以太网RGMII接口   
    input              rgmii_rxc   , //RGMII接收时钟
    input              rgmii_rx_ctl, //RGMII接收数据控制信号
    input       [3:0]  rgmii_rxd   , //RGMII接收数据
    output             rgmii_txc   , //RGMII发送时钟    
    output             rgmii_tx_ctl, //RGMII发送数据控制信号
    output      [3:0]  rgmii_txd     //RGMII发送数据          
    );
//wire
wire   pll_lock  ;
wire   gmii_tx_er;
//*****************************************************
//**                    main code
//*****************************************************
assign gmii_tx_clk = gmii_rx_clk;
//RGMII接收
rgmii_rx u_rgmii_rx(
    .rgmii_rxc        (rgmii_rxc      ),
    .rgmii_rx_ctl     (rgmii_rx_ctl   ),
    .rgmii_rxd        (rgmii_rxd      ),
                      
    .gmii_rx_clk      (gmii_rx_clk    ),
    .gmii_rx_dv       (gmii_rx_dv     ),
    .gmii_rxd         (gmii_rxd       ),
    .gmii_tx_clk_deg  (gmii_tx_clk_deg),
    .pll_lock         (pll_lock       )
    );

//RGMII发送
rgmii_tx u_rgmii_tx(
    .reset            (1'b0           ),

    .gmii_tx_er       (1'b0           ),
    .gmii_tx_clk      (gmii_tx_clk    ),
    .gmii_tx_en       (gmii_tx_en     ),
    .gmii_txd         (gmii_txd       ),
    .gmii_tx_clk_deg  (gmii_tx_clk_deg),
    
    .rgmii_txc        (rgmii_txc      ),
    .rgmii_tx_ctl     (rgmii_tx_ctl   ),
    .rgmii_txd        (rgmii_txd      )
    );

endmodule

由该模块的端口可知,该模块实现了双沿(DDR)数据和单沿(SDR)数据之间的转换。程序中第 23 行将 GMII 接收时钟赋值给 GMII 发送时钟,因此 GMII 的发送时钟和接收时钟实际上为同一个时钟。GMII TO RGMII 模块例化了 rgmii_rx 模块和 rgmii_tx 模块。

rgmii_rx 模块代码如下所示:

module rgmii_rx(
    //以太网RGMII接口
    input              rgmii_rxc      , //RGMII接收时钟
    input              rgmii_rx_ctl   , //RGMII接收数据控制信号
    input       [3:0]  rgmii_rxd      , //RGMII接收数据    

    //以太网GMII接口
    output             gmii_rx_clk    , //GMII接收时钟
    output  reg        gmii_rx_dv     , //GMII接收数据有效信号
    output  reg  [7:0] gmii_rxd       , //GMII接收数据 
    output             gmii_tx_clk_deg,
    output             pll_lock
    );
    
//define wire
wire            gmii_rx_dv_s;
wire  [ 7:0]    gmii_rxd_s;
//*****************************************************
//**                    main code
//*****************************************************
pll_sft U_pll_phase_shift(   
    .clkout0   (gmii_rx_clk    ),   //125MHz
    .clkout1   (gmii_tx_clk_deg),
    .clkin1    (rgmii_rxc      ),
    .clkfb     (gmii_rx_clk    ),
    .pll_rst   (1'b0           ),
    .pll_lock  (pll_lock       )
    );
    

always @(posedge gmii_rx_clk)
begin
    gmii_rxd   = gmii_rxd_s;
    gmii_rx_dv = gmii_rx_dv_s;
end

wire [5:0] nc1;
GTP_ISERDES #(
    .ISERDES_MODE    ("IDDR"),  //"IDDR","IMDDR","IGDES4","IMDES4","IGDES7","IGDES8","IMDES8"
    .GRS_EN          ("TRUE"),  //"TRUE"; "FALSE"
    .LRS_EN          ("TRUE")   //"TRUE"; "FALSE"
) igddr1(         
    .DI              (rgmii_rxd[0]),
    .ICLK            (1'd0        ),
    .DESCLK          (gmii_rx_clk ),
    .RCLK            (gmii_rx_clk ),
    .WADDR           (3'd0        ),
    .RADDR           (3'd0        ),
    .RST             (1'b0        ),
    .DO              ({gmii_rxd_s[4],gmii_rxd_s[0],nc1})
);

wire [5:0] nc2;
GTP_ISERDES #(
    .ISERDES_MODE    ("IDDR"),  //"IDDR","IMDDR","IGDES4","IMDES4","IGDES7","IGDES8","IMDES8"
    .GRS_EN          ("TRUE"),  //"TRUE"; "FALSE"
    .LRS_EN          ("TRUE")   //"TRUE"; "FALSE"
) igddr2(
    .DI              (rgmii_rxd[1]),
    .ICLK            (1'd0        ),
    .DESCLK          (gmii_rx_clk ),
    .RCLK            (gmii_rx_clk ),
    .WADDR           (3'd0        ),
    .RADDR           (3'd0        ),
    .RST             (1'b0        ),
    .DO              ({gmii_rxd_s[5],gmii_rxd_s[1],nc2})
);

wire [5:0] nc3;
GTP_ISERDES #(
    .ISERDES_MODE    ("IDDR"),  //"IDDR","IMDDR","IGDES4","IMDES4","IGDES7","IGDES8","IMDES8"
    .GRS_EN          ("TRUE"),  //"TRUE"; "FALSE"
    .LRS_EN          ("TRUE")   //"TRUE"; "FALSE"
) igddr3(
    .DI              (rgmii_rxd[2]),
    .ICLK            (1'd0        ),
    .DESCLK          (gmii_rx_clk ),
    .RCLK            (gmii_rx_clk ),
    .WADDR           (3'd0        ),
    .RADDR           (3'd0        ),
    .RST             (1'b0        ),
    .DO              ({gmii_rxd_s[6],gmii_rxd_s[2],nc3})
);

wire [5:0] nc4;
GTP_ISERDES #(
    .ISERDES_MODE    ("IDDR"),  //"IDDR","IMDDR","IGDES4","IMDES4","IGDES7","IGDES8","IMDES8"
    .GRS_EN          ("TRUE"),  //"TRUE"; "FALSE"
    .LRS_EN          ("TRUE")   //"TRUE"; "FALSE"
) igddr4(
    .DI              (rgmii_rxd[3]),
    .ICLK            (1'd0        ),
    .DESCLK          (gmii_rx_clk ),
    .RCLK            (gmii_rx_clk ),
    .WADDR           (3'd0        ),
    .RADDR           (3'd0        ),
    .RST             (1'b0        ),
    .DO              ({gmii_rxd_s[7],gmii_rxd_s[3],nc4})
);

wire [5:0] nc5;
GTP_ISERDES #(
    .ISERDES_MODE    ("IDDR"),  //"IDDR","IMDDR","IGDES4","IMDES4","IGDES7","IGDES8","IMDES8"
    .GRS_EN          ("TRUE"),  //"TRUE"; "FALSE"
    .LRS_EN          ("TRUE")   //"TRUE"; "FALSE"
) igddr5(
    .DI              (rgmii_rx_ctl),
    .ICLK            (1'd0        ),
    .DESCLK          (gmii_rx_clk ),
    .RCLK            (gmii_rx_clk ),
    .WADDR           (3'd0        ),
    .RADDR           (3'd0        ),
    .RST             (1'b0        ),
    .DO              ({rgmii_rx_ctl_s,gmii_rx_dv_s,nc5})
);

endmodule

该模块通过调用 GTP_ISERDES 原语,实现了 RGMII 接口输入的 DDR 数据到 SDR 数据的转换,输入的 rgmii_rx_ctl 控制信号的转换方法同样类似。rgmii_rx 模块信号转换示意图如下图所示:

在这里插入图片描述
时钟专用引脚输入的 rgmii_rxc 时钟经过 PLL 后,得到 rgmii_rx_clk

rgmii_rx_ctl 控制信号和 4rgmii_rxd 数据先经过 GTP_ISERDESIDDR 模式) 将双沿 1 位数据转换成单沿两位数据。

另外,在程序的第 40 行至 122 行代码是对 GTP_ISERDES 的 5 次例化。

rgmii_tx 模块代码如下所示:

module rgmii_tx(
    input              reset,
    //GMII发送端口
    input              gmii_tx_er     , //GMII输出数据有效信号
    input              gmii_tx_clk    , //GMII发送时钟    
    input              gmii_tx_en     , //GMII发送数据使能信号
    input       [7:0]  gmii_txd       , //GMII输出数据    
    input              gmii_tx_clk_deg, //GMII发送时钟相位偏移45度
    //RGMII发送端口
    output             rgmii_txc      , //RGMII发送数据时钟    
    output             rgmii_tx_ctl   , //RGMII输出数据有效信号
    output      [3:0]  rgmii_txd        //RGMII输出数据
    );
// registers
reg             tx_reset_d1    ;
reg             tx_reset_sync  ;
reg             rx_reset_d1    ;

reg   [ 7:0]    gmii_txd_r     ;
reg   [ 7:0]    gmii_txd_r_d1  ;

reg             gmii_tx_en_r   ;
reg             gmii_tx_en_r_d1;

reg             gmii_tx_er_r   ;

reg             rgmii_tx_ctl_r ;
reg   [ 3:0]    gmii_txd_low   ;

// wire
wire            padt1   ;
wire            padt2   ;
wire            padt3   ;
wire            padt4   ;
wire            padt5   ;
wire            padt6   ;
wire            stx_txc ;
wire            stx_ctr ;
wire  [3:0]     stxd_rgm;
//*****************************************************
//**                    main code
//*****************************************************
always @(posedge gmii_tx_clk) begin
    tx_reset_d1   <= reset;
    tx_reset_sync <= tx_reset_d1;
end

always @(posedge gmii_tx_clk) begin
    if (tx_reset_sync == 1'b1) begin
        gmii_txd_r   <= 8'h0;
        gmii_tx_en_r <= 1'b0;
        gmii_tx_er_r <= 1'b0;
    end
    else
    begin
        gmii_txd_r      <= gmii_txd;
        gmii_tx_en_r    <= gmii_tx_en;
        gmii_tx_er_r    <= gmii_tx_er;
        gmii_txd_r_d1   <= gmii_txd_r;
        gmii_tx_en_r_d1 <= gmii_tx_en_r;
    end
end

always @(posedge gmii_tx_clk)
begin
    rgmii_tx_ctl_r = gmii_tx_en_r ^ gmii_tx_er_r;
    gmii_txd_low   = gmii_txd_r[7:4];
end

//输出双沿采样寄存器 (rgmii_txd)
GTP_OSERDES #(
    .OSERDES_MODE    ("ODDR" ),  //"ODDR","OMDDR","OGSER4","OMSER4","OGSER7","OGSER8",OMSER8"
    .WL_EXTEND       ("FALSE"),  //"TRUE"; "FALSE"
    .GRS_EN          ("TRUE" ),  //"TRUE"; "FALSE"
    .LRS_EN          ("TRUE" ),  //"TRUE"; "FALSE"
    .TSDDR_INIT      (1'b0   )   //1'b0;1'b1
) gtp_ogddr6(     
    .DO              (stx_txc        ),
    .TQ              (padt6          ),
    .DI              ({7'd0,1'b1}    ),
    .TI              (4'd0           ),
    .RCLK            (gmii_tx_clk_deg),
    .SERCLK          (gmii_tx_clk_deg),
    .OCLK            (1'd0           ),
    .RST             (tx_reset_sync  )
);
GTP_OUTBUFT  gtp_outbuft6
(
    .I    (stx_txc  ),
    .T    (padt6    ),
    .O    (rgmii_txc)
);


GTP_OSERDES #(
    .OSERDES_MODE    ("ODDR" ),  //"ODDR","OMDDR","OGSER4","OMSER4","OGSER7","OGSER8",OMSER8"
    .WL_EXTEND       ("FALSE"),  //"TRUE"; "FALSE"
    .GRS_EN          ("TRUE" ),  //"TRUE"; "FALSE"
    .LRS_EN          ("TRUE" ),  //"TRUE"; "FALSE"
    .TSDDR_INIT      (1'b0   )   //1'b0;1'b1
) gtp_ogddr2(     
    .DO              (stxd_rgm[3]  ),
    .TQ              (padt2        ),
    .DI              ({6'd0,gmii_txd_low[3],gmii_txd_r_d1[3]}),
    .TI              (4'd0         ),
    .RCLK            (gmii_tx_clk  ),
    .SERCLK          (gmii_tx_clk  ),
    .OCLK            (1'd0         ),
    .RST             (tx_reset_sync)
); 
GTP_OUTBUFT  gtp_outbuft2
(
    .I    (stxd_rgm[3]),
    .T    (padt2      ),
    .O    (rgmii_txd[3])
);


GTP_OSERDES #(
    .OSERDES_MODE    ("ODDR" ),  //"ODDR","OMDDR","OGSER4","OMSER4","OGSER7","OGSER8",OMSER8"
    .WL_EXTEND       ("FALSE"),  //"TRUE"; "FALSE"
    .GRS_EN          ("TRUE" ),  //"TRUE"; "FALSE"
    .LRS_EN          ("TRUE" ),  //"TRUE"; "FALSE"
    .TSDDR_INIT      (1'b0   )   //1'b0;1'b1
) gtp_ogddr3(     
    .DO              (stxd_rgm[2]  ),
    .TQ              (padt3        ),
    .DI              ({6'd0,gmii_txd_low[2],gmii_txd_r_d1[2]}),
    .TI              (4'd0         ),
    .RCLK            (gmii_tx_clk  ),
    .SERCLK          (gmii_tx_clk  ),
    .OCLK            (1'd0         ),
    .RST             (tx_reset_sync)
); 
GTP_OUTBUFT  gtp_outbuft3
(    
    .I    (stxd_rgm[2]),
    .T    (padt3      ),
    .O    (rgmii_txd[2])
);


GTP_OSERDES #(
    .OSERDES_MODE    ("ODDR" ),  //"ODDR","OMDDR","OGSER4","OMSER4","OGSER7","OGSER8",OMSER8"
    .WL_EXTEND       ("FALSE"),  //"TRUE"; "FALSE"
    .GRS_EN          ("TRUE" ),  //"TRUE"; "FALSE"
    .LRS_EN          ("TRUE" ),  //"TRUE"; "FALSE"
    .TSDDR_INIT      (1'b0   )   //1'b0;1'b1
) gtp_ogddr4(     
    .DO              (stxd_rgm[1]  ),
    .TQ              (padt4        ),
    .DI              ({6'd0,gmii_txd_low[1],gmii_txd_r_d1[1]}),
    .TI              (4'd0         ),
    .RCLK            (gmii_tx_clk  ),
    .SERCLK          (gmii_tx_clk  ),
    .OCLK            (1'd0         ),
    .RST             (tx_reset_sync)
); 
GTP_OUTBUFT  gtp_outbuft4
(
    .I    (stxd_rgm[1]),
    .T    (padt4      ),
    .O    (rgmii_txd[1])
);


GTP_OSERDES #(
    .OSERDES_MODE    ("ODDR" ),  //"ODDR","OMDDR","OGSER4","OMSER4","OGSER7","OGSER8",OMSER8"
    .WL_EXTEND       ("FALSE"),  //"TRUE"; "FALSE"
    .GRS_EN          ("TRUE" ),  //"TRUE"; "FALSE"
    .LRS_EN          ("TRUE" ),  //"TRUE"; "FALSE"
    .TSDDR_INIT      (1'b0   )   //1'b0;1'b1
) gtp_ogddr5(     
    .DO              (stxd_rgm[0]  ),
    .TQ              (padt5        ),
    .DI              ({6'd0,gmii_txd_low[0],gmii_txd_r_d1[0]}),
    .TI              (4'd0         ),
    .RCLK            (gmii_tx_clk  ),
    .SERCLK          (gmii_tx_clk  ),
    .OCLK            (1'd0         ),
    .RST             (tx_reset_sync)
); 
GTP_OUTBUFT  gtp_outbuft5
(
    .I    (stxd_rgm[0]),
    .T    (padt5      ),
    .O    (rgmii_txd[0])
);


//输出双沿采样寄存器 (rgmii_tx_ctl)
GTP_OSERDES #( 
    .OSERDES_MODE    ("ODDR" ),  //"ODDR","OMDDR","OGSER4","OMSER4","OGSER7","OGSER8",OMSER8"
    .WL_EXTEND       ("FALSE"),  //"TRUE"; "FALSE"
    .GRS_EN          ("TRUE" ),  //"TRUE"; "FALSE"
    .LRS_EN          ("TRUE" ),  //"TRUE"; "FALSE"
    .TSDDR_INIT      (1'b0   )   //1'b0;1'b1
) gtp_ogddr1(     
    .DO              (stx_ctr      ),
    .TQ              (padt1        ),
    .DI              ({6'd0,rgmii_tx_ctl_r,gmii_tx_en_r_d1}),
    .TI              (4'd0         ),
    .RCLK            (gmii_tx_clk  ),
    .SERCLK          (gmii_tx_clk  ),
    .OCLK            (1'd0         ),
    .RST             (tx_reset_sync)
); 
GTP_OUTBUFT  gtp_outbuft1
(
    .I    (stx_ctr     ),
    .T    (padt1       ),
    .O    (rgmii_tx_ctl)
);

endmodule

该模块通过调用 ODDR 原语将输入的单沿 8 位数据(gmii_txd)转换成双沿采样的 4 位数据(rgmii_txd),gmii_tx_enrgmii_tx_ctl 信号的处理方法同样类似。rgmii_tx 模块信号转换示意图如下图所示:

在这里插入图片描述
gmii_tx_en 数据使能信号、gmii_tx_er 输出数据有效信号和 8gmii_txd 数据经过 GTP_OSERDESODDR 模式将单沿 2 位数据转换成双沿 1 位数据。GTP_OSERDES 通常跟 GTP_OUTBUF 一起使用。


3.3 ARP 模块设计

ARP 顶层模块实现了整个以太网帧格式与 ARP 协议的功能,ARP 顶层模块例化了 ARP 接收模块(arp_rx)、ARP 发送模块(arp_tx)和 CRC 校验模块(crc32_d8)。

ARP 接收模块(arp_rx):ARP 接收模块负责解析以太网的数据,判断目的 MAC 地址和目的 IP 地址是否为开发板的地址,然后按照 ARP 协议将数据解析出来。当解析到正确的 ARP 数据包后,拉高 arp_rx_done 信号,持续一个时钟周期。

arp_rx_type 用于表示 ARP 数据包的类型,0 表示收到 ARP 请求包,1 表示收到 ARP 应答包。src_macsrc_ip 分别是解析出的对端设备 MAC 地址和 IP 地址。

ARP 发送模块(arp_tx):ARP 发送模块根据以太网帧格式和 ARP 协议发送 ARP 请求或者 ARP 应答数据。 arp_tx_enarp_tx_type 分别表示 ARP 发送模块的使能信号和发送 ARP 类型。dec_macdec_ip 分别设置对端设备 MAC 地址和 IP 地址。

CRC 校验模块(crc32_d8):CRC 校验模块是对 ARP 发送模块的数据(不包括前导码和帧起始界定符) 做校验,把校验结果值拼在以太网帧格式的 FCS 字段, 如果 CRC 校验值计算错误或者没有的话, 那么电脑网卡会直接丢弃该帧导致收不到数据(有些网卡是可以设置不做校验的) 。CRC32 校验在 FPGA 实现的原理是 LFSRLinear Feedback Shift Register,线性反馈移位寄存器),其思想是各个寄存器储存着上一次 CRC32 运算的结果,寄存器的输出即为 CRC32 的值。需要说明的是,本次实验只对发送模块做校验,没有对接收模块做校验。这是由于我们可以直接通过解析出的数据来大致判断接收是否正确,而发送模块必须发送正确的校验数据,否则发送的数据直接被电脑的网卡丢弃,导致 ARP 请求或者应答失败。

在简介部分我向大家介绍过,ARP 的数据包格式包括 前导码 + SFD、以太网帧头、ARP 数据(包括填充部分数据)和 CRC 校验。在接收以太网数据的过程中,这些不同部分的数据可以刚好对应状态机的不同状态位,因此我们可以通过状态机来解析以太网的数据。

ARP 接收模块通过状态机来解析数据,其状态跳转图如下图所示:

在这里插入图片描述

接收模块使用三段式状态机来解析以太网包,从上图可以比较直观的看到每个状态实现的功能以及跳转到下一个状态的条件。

这里需要注意的一点是,在中间状态如前导码错误、MAC 地址错误以及 IP 地址等错误时跳转到 st_rx_end 状态,而不是跳转到 st_idle 状态。因为中间状态在解析到数据错误时,单包数据的接收还没有结束,如果此时跳转到 st_idle 状态会误把有效数据当成前导码来解析,所以状态跳转到 st_rx_end。而 gmii_rx_dv 信号为 0 时,单包数据才算接收结束,所以 st_rx_end 跳转到 st_idle 的条件是 eth_rxdv = 0,准备接收下一包数据。

因为代码较长,我只粘贴了第三段状态机的接收 ARP 数据状态和接收结束状态源代码,代码如下:

在这里插入图片描述

st_arp_data 状态根据 ARP 协议解析数据,在程序的第 16 行至第 17 行代码判断目的 IP 地址和 OP 操作码是否正确,如果错误,则丢弃该包数据。在程序的第 211 行至第 214 行代码根据操作码为 arp_rx_type(接收到的 ARP 数据包类型)赋值,当接收到 ARP 请求包时,arp_rx_type 等于 0;当接收到 ARP 应答包时,arp_rx_type 等于 1

ARP 接收过程中仿真的波形图如下图所示gmii_rx_dv 信号拉高表示此时输入的数据有效,根据 gmii_rxd 的值来解析数据。从图中可以看出, 发送端的 MAC 地址和 ip 地址,以及当前接收到的以太网数据包类型为 ARP0x0806)。在接收完 ARP 数据包之后,拉高 arp_rx_done 信号表示接收完成,图中 arp_rx_type 信号为低电平,表示当前接收到的是 ARP 请求数据包。

arp 模块的仿真代码如下所示:

module  tb_arp;

//parameter  define
parameter  T = 8;                       //时钟周期为8ns
parameter  OP_CYCLE = 100;              //操作周期

//开发板MAC地址 00-11-22-33-44-55
parameter  BOARD_MAC = 48'h00_11_22_33_44_55;     
//开发板IP地址 192.168.1.10     
parameter  BOARD_IP  = {8'd192,8'd168,8'd1,8'd10};
//目的MAC地址 ff_ff_ff_ff_ff_ff
parameter  DES_MAC   = 48'hff_ff_ff_ff_ff_ff;
//目的IP地址 192.168.1.10
parameter  DES_IP    = {8'd192,8'd168,8'd1,8'd10};

//reg define
reg           gmii_clk;    //时钟信号
reg           sys_rst_n;   //复位信号

reg           arp_tx_en  ; //ARP发送使能信号
reg           arp_tx_type; //ARP发送类型 0:请求  1:应答
reg   [3:0]   flow_cnt   ;
reg   [13:0]  delay_cnt  ;

wire          gmii_rx_clk; //GMII接收时钟
wire          gmii_rx_dv ; //GMII接收数据有效信号
wire  [7:0]   gmii_rxd   ; //GMII接收数据
wire          gmii_tx_clk; //GMII发送时钟
wire          gmii_tx_en ; //GMII发送数据使能信号
wire  [7:0]   gmii_txd   ; //GMII发送数据
              
wire          arp_rx_done; //ARP接收完成信号
wire          arp_rx_type; //ARP接收类型 0:请求  1:应答
wire  [47:0]  src_mac    ; //接收到目的MAC地址
wire  [31:0]  src_ip     ; //接收到目的IP地址    
wire  [47:0]  des_mac    ; //发送的目标MAC地址
wire  [31:0]  des_ip     ; //发送的目标IP地址
wire          tx_done    ; //以太网发送完成信号 

//*****************************************************
//**                    main code
//*****************************************************

assign gmii_rx_clk = gmii_clk   ;
assign gmii_tx_clk = gmii_clk   ;
assign gmii_rx_dv  = gmii_tx_en ;
assign gmii_rxd    = gmii_txd   ;

assign des_mac = src_mac;
assign des_ip  = src_ip;

//给输入信号初始值
initial begin
    gmii_clk           = 1'b0;
    sys_rst_n          = 1'b0;     //复位
    #(T+1)  sys_rst_n  = 1'b1;     //在第(T+1)ns的时候复位信号信号拉高
end

//125Mhz的时钟,周期则为1/125Mhz=8ns,所以每4ns,电平取反一次
always #(T/2) gmii_clk = ~gmii_clk;

always @(posedge gmii_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        arp_tx_en <= 1'b0;
        arp_tx_type <= 1'b0;
        delay_cnt <= 1'b0;
        flow_cnt <= 1'b0;
    end
    else begin
        case(flow_cnt)
            'd0 : flow_cnt <= flow_cnt + 1'b1;
            'd1 : begin
                arp_tx_en <= 1'b1;
                arp_tx_type <= 1'b0;  //发送ARP请求
                flow_cnt <= flow_cnt + 1'b1;
            end
            'd2 : begin 
                arp_tx_en <= 1'b0;
                flow_cnt <= flow_cnt + 1'b1;
            end    
            'd3 : begin
                if(tx_done)
                    flow_cnt <= flow_cnt + 1'b1;
            end
            'd4 : begin
                delay_cnt <= delay_cnt + 1'b1;
                if(delay_cnt == OP_CYCLE - 1'b1)
                    flow_cnt <= flow_cnt + 1'b1;
            end
            'd5 : begin
                arp_tx_en <= 1'b1;
                arp_tx_type <= 1'b1;  //发送ARP应答   
                flow_cnt <= flow_cnt + 1'b1;                
            end
            'd6 : begin 
                arp_tx_en <= 1'b0;
                flow_cnt <= flow_cnt + 1'b1;
            end 
            'd7 : begin
                if(tx_done)
                    flow_cnt <= flow_cnt + 1'b1;
            end
            default:;
        endcase    
    end
end

//ARP通信
arp                                             
   #(
    .BOARD_MAC     (BOARD_MAC),      //参数例化
    .BOARD_IP      (BOARD_IP ),
    .DES_MAC       (DES_MAC  ),
    .DES_IP        (DES_IP   )
    )
   u_arp(
    .rst_n         (sys_rst_n  ),
                    
    .gmii_rx_clk   (gmii_rx_clk),
    .gmii_rx_dv    (gmii_rx_dv ),
    .gmii_rxd      (gmii_rxd   ),
    .gmii_tx_clk   (gmii_tx_clk),
    .gmii_tx_en    (gmii_tx_en ),
    .gmii_txd      (gmii_txd   ),
                    
    .arp_rx_done   (arp_rx_done),
    .arp_rx_type   (arp_rx_type),
    .src_mac       (src_mac    ),
    .src_ip        (src_ip     ),
    .arp_tx_en     (arp_tx_en  ),
    .arp_tx_type   (arp_tx_type),
    .des_mac       (des_mac    ),
    .des_ip        (des_ip     ),
    .tx_done       (tx_done    )
    );

endmodule

14 行代码将目的 ip 地址改写成与开发板的 ip 地址一样,因为仿真的原理是 arp 的环回,既将 arp 的发送数据直接发送给 arp 的接收数据,如果两个 ip 地址不一样 error_en 信号会拉高报错。

ARP 接收采集波形图如下图所示。
在这里插入图片描述
ARP 发送模块则是根据以太网帧格式是 ARP 协议发送数据,也就是接收模块的逆过程, 同样也非常适合使用状态机来完成发送数据的功能,状态跳转图如下图所示:

在这里插入图片描述

发送模块接收模块有很多相似之处,同样使用三段式状态机来发送以太网包,从上图可以比较直观的看到每个状态实现的功能以及跳转到下一个状态的条件。

发送模块的代码中定义了数组来存储前导码 + 帧头以太网的帧头ARP 数据,在复位时初始化数组的值,部分源代码如下。

在这里插入图片描述
省略部分代码…(完整代码见本文开头提供的开源地址)

在这里插入图片描述

以上代码在复位时对数组进行初始化。

在这里插入图片描述

以上程序的第 5 行至 26 行代码,根据输入的发送类型目的 MAC 地址IP 地址,重新更新数组里的值。

在这里插入图片描述

以上程序第 1 行至第 18 行代码为发送 ARP 数据的状态。我前面讲过以太网帧格式的数据部分最少是 46 个字节,ARP 数据只有 28 个字节,因此在发送完 ARP 数据之后补充发送 18 个字节,填充的数据为 0

在这里插入图片描述

以上程序的第 1 行至 23 行代码为发送 CRC 校验值状态,发送模块的 CRC 校验是由 crc32_d8 模块完成的,发送模块将输入的 crc 的计算结果每 4 位高低位互换,按位取反发送出去,crc 计算部分在后面阐述。

ARP 发送过程中采集的波形图如下图所示,arp_tx_en 信号作为开始发送 ARP 数据包的触发信号,arp_tx_type 为高电平,表示发送 ARP 应答数据包。ARP 应答数据包中的目的 MAC 地址和目的 IP 地址从 ARP 接收数据包中获取,gmii_tx_en 拉高,表示 gmii_txd 数据有效,在发送完 ARP 数据包后,输出一个脉冲信号(tx_done),表示发送完成。

在这里插入图片描述

CRC 校验模块主要完成对 ARP 发送模块数据的校验,由于代码较长,这里不再贴出代码。CRC32 校验在 FPGA 实现的原理是线性反馈移位寄存器,其思想是各个寄存器储存着上一次 CRC32 运算的结果,寄存器的输出即为 CRC32 的值。

CRC32 的原理与公式推导较复杂,在此可不必深究。

CRC32 的生成多项式为:G(x)= x^32 + x^26 + x^23 + x^22 + x^16 + x^12 + x^11 + x^10 + x^8 + x^7 + x^5 + x^4 + x^2 + x^1 +1,只需稍作修改就可以直接使用。

ARP 控制模块的代码如下:

module arp_ctrl(
    input                clk        , //输入时钟   
    input                rst_n      , //复位信号,低电平有效
    
    input                touch_key  , //触摸按键,用于触发开发板发出ARP请求
    input                arp_rx_done, //ARP接收完成信号
    input                arp_rx_type, //ARP接收类型 0:请求  1:应答 
    output  reg          arp_tx_en  , //ARP发送使能信号
    output  reg          arp_tx_type  //ARP发送类型 0:请求  1:应答
    );

//reg define
reg         touch_key_d0;
reg         touch_key_d1;

//wire define
wire        pos_touch_key;  //touch_key信号上升沿

//*****************************************************
//**                    main code
//*****************************************************

assign pos_touch_key = ~touch_key_d1 & touch_key_d0;

//对arp_tx_en信号延时打拍两次,用于采touch_key的上升沿
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        touch_key_d0 <= 1'b0;
        touch_key_d1 <= 1'b0;
    end
    else begin
        touch_key_d0 <= touch_key;
        touch_key_d1 <= touch_key_d0;
    end
end

//为arp_tx_en和arp_tx_type赋值
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        arp_tx_en <= 1'b0;
        arp_tx_type <= 1'b0;
    end
    else begin
        if(pos_touch_key == 1'b1) begin  //检测到输入触摸按键上升沿
            arp_tx_en <= 1'b1;           
            arp_tx_type <= 1'b0;
        end
        //接收到ARP请求,开始控制ARP发送模块应答
        else if((arp_rx_done == 1'b1) && (arp_rx_type == 1'b0)) begin
            arp_tx_en <= 1'b1;
            arp_tx_type <= 1'b1;
        end
        else
            arp_tx_en <= 1'b0;
    end
end

endmodule

ARP 控制模块的代码较简单,首先检测输入触摸按键的上升沿,当检测到上升沿之后,触发 ARP 顶层模块发起 ARP 请求信号;同时检测输入的 arp_rx_donearp_rx_type 信号,当接收上位机的 ARP 请求信号后,触发 ARP 顶层模块发送 ARP 应答信号,将开发板的 MAC 地址发送给上位机。



4 下载验证

程序设计完成并仿真通过后,接下来我们就可以上板下载验证了,下面会详细讲解如何进行本实验的验证。

编译工程并生成比特流 .sbit 文件后,此时将下载器一端连接电脑,另一端与开发板上的 JTAG 下载口连接,将网线一端连接开发板的网口,另一端连接电脑的网口或者路由器,接下来连接电源线,并打开开发板的电源开关,将 PDS 生成好的 .sbit 流文件下载到开发板中去。

程序下载完成后,PHY 芯片会和电脑网卡进行通信(自协商),如果程序下载正确并且硬件连接无误的话,我们点击电脑右下角的网络图标,会看到本地连接刚开始显示的是正在识别,一段时间之后显示未识别的网络,打开方式如下图所示(不同的 Windows 版本操作可能存在差异,但基本相同)。

在这里插入图片描述

点击上图中的 “未识别的网络(无 Internet)”,弹出如下图所示界面。

在这里插入图片描述

点击 “更改适配器” 选项,弹出如下图所示界面。

在这里插入图片描述

如果看到上图 “以太网” 显示未识别的网络之后,说明硬件连接和程序都是没有问题的,接下来设置以太网的 IP 地址,改成代码中设置的目的 IP 地址,顶层模块参数定义如下:

//目的 IP 地址 192.168.1.102
parameter DES_IP = {8'd192,8'd168,8'd1,8'd102};

因此接下来将电脑以太网的 IP 地址设置成 192.168.1.102。鼠标右击上图中的以太网,如下图所示:

在这里插入图片描述

点击 “属性”,弹出如下图所示界面。

在这里插入图片描述

鼠标双击 “Internet 协议版本 4(TCP/IPv4)”,弹出如下图所示界面。

在这里插入图片描述

“Internet 协议版本 4(TCP/IPv4)” 属性界面中,选择使用下面的 IP 地址,IP 地址设置成 192.168.1.102,并点击确定完成设置。

接下来以管理员身份打开电脑的命令的 DOS 命令窗口(注意必须以管理员身份打开),打开方式如下所示。

在这里插入图片描述

打开 DOS 命令窗口后,在命令行中输入 “arp -a”,如下图所示。

在这里插入图片描述

输入完成后,按下键盘的回车键,此时会弹出电脑中所有网络接口的 ARP 缓存表,我们只需要关注以太网接口的 ARP 缓存表(IP 地址为 192.168.1.102),如下图所示。

在这里插入图片描述

可以发现,此时 ARP 缓存表中还没有开发板的 MAC 地址和 IP 地址,此时我们按下开发板的触摸按键(TPAD)。按下后,开发板会向电脑发起 ARP 请求,并且电脑会返回自己的 MAC 地址到开发板。

需要说明的是,在开发板发起 ARP 请求时,会将开发板的 MAC 地址和 IP 地址都发给电脑,此时电脑就已经获取到了开发板的 MAC 地址和 IP 地址,并更新至 ARP 的缓存表中,我们重新在 DOS 命令中输入 “arp -a”,如下面两张图所示。

在这里插入图片描述

在这里插入图片描述

由上图可知,此时以太网接口的 ARP 缓存表中已经添加了开发板的 IP 地址(192.168.1.10)和 MAC 地址(00-11-22-33-44-55),说明开发板发送 ARP 请求成功。如果大家操作失败,请检查开发板的网口是否通过网线连接电脑的网口,并且此时开发板已经下载程序以及通过按下触摸按键(TPAD)来触发 ARP 请求。另外,如果电脑网口不支持千兆网通信,那么也会导致 ARP 操作失败。

接下来我们再来通过电脑发起 ARP 请求,验证开发板有没有正确返回 ARP 应答。我们先从以太网 ARP 缓存表中删除开发板的 MAC 地址,删除方法是在 DOS 命令中输入 “arp -d”,并按下按键的回车键,如下图所示。

在这里插入图片描述

接下来重新在 DOS 命令中输入 “arp -a”,来验证是否删除成功(如果没有以管理员运行会导致删除失败),输入完成后,如下图所示。

在这里插入图片描述

此时我们之前获取的开发板 MAC 地址已经删除成功,接下来在 DOS 命令中输入 “ping 192.168.1.10”,来让电脑发起 ARP 请求,如下图所示。

在这里插入图片描述

需要说明的是,ping 是一个十分强大的 TCP/IP 工具,它可以用来检测网络的连通情况和分析网络速度。ping 命令是一个固定格式的 ICMPInternet 控制报文协议)请求数据包,之后会发起 ARP 请求命令,所以我们这里是通过 ping 命令来间接发起 ARP 请求。由于开发板并没有实现 ICMP 协议,因此在 ping 时会请求超时,但是在 ping 的过程中发起的 ARP 请求,开发板会响应并返回 ARP 应答数据。

接下来再次在 DOS 命令中输入 “arp -a”,查询是否成功获取到开发板 MAC 地址,如下图所示。

在这里插入图片描述

由上图可知,电脑正确获取到开发板的 MAC 地址,并更新至 ARP 缓存表中。到这里,开发板实现的 ARP 协议就已经全部验证成功了。

接下来介绍一个以太网通信时经常使用的抓包软件 Wireshark,可以直接在网上搜索下载,我们现在打开 Wireshark,界面如下图所示。

在这里插入图片描述

双击上图所示的以太网或者先选中以太网,再点击上方红框选中的蓝色按钮,即可开始抓取本地连接的数据包,抓取界面如下图所示。

在这里插入图片描述

从上图可以看到,已经抓取到其它应用程序使用以太网发送的数据包,但是这些数据包并不是开发板发送的数据包,我们这个时候按下开发板的触摸按键 TPAD(根据自己开发板来按),就可以在 wireshark 中抓取到数据包了,抓取到的数据包如下图所示。

在这里插入图片描述

上图中第 38 行数据包是开发板发送给电脑的 ARP 请求包,第 39 行数据包是电脑发送给开发板的 ARP 应答包,此时双击第 38 行即可看到开发板发送的详细数据,如下图所示。

在这里插入图片描述

上图中下方红框为开发板发送的 16 进制数据(去掉前导码、SFDCRC 值),可以看到,后面的 180 就是我们在发送时填充的 18 个字节数据。需要说明的是,当打开第 39 行电脑返回的 ARP 请求包时,看不到填充的 0,这是由于后面填充的数据是网卡自动填充的,因此 wireshark 中会看不到。



5 总结

至此,本专栏中关于 FPGA 以太网入门的第二篇 —— ARP测试实验已经全部讲解完毕,全文篇幅较长,爆肝4万字,建议收藏后精读。

希望以上的内容对您有所帮助,诚挚地欢迎各位读者在评论区或者私信我交流!

微博:沂舟Ryan (@沂舟Ryan 的个人主页 - 微博 )

GitHub:ChinaRyan666

微信公众号:沂舟无限进步(内含精品资料及详细教程)

如果对您有帮助的话请点赞支持下吧!

集中一点,登峰造极。

  • 29
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ChinaRyan666

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值