1)实验平台:正点原子MPSoC开发板
2)平台购买地址:https://detail.tmall.com/item.htm?id=692450874670
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/thread-340252-1-1.html
在以太网中,一个主机和另一个主机进行通信,必须要知道目的主机的MAC地址(物理地址),而目的MAC地址的获取由ARP协议完成。本章我们来学习如何通过DFZU2EG/4EV MPSoC开发板实现ARP协议的功能。
本章分为以下几个章节:
222828.1简介
28.2实验任务
28.3硬件设计
28.4程序设计
28.5下载验证
28.1简介
ARP(Address 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表,此类比较麻烦而且会影响网络性能。
动态映射指使用协议来获取相对应的物理地址,之所以用动态这个词是因为这个过程是自动完成的,一般应用程序的用户或系统管理员不必关心。已经设计出用于实现动态映射协议的有ARP和RARP(逆地址解析协议)两种,如下图所示。
图 28.1.1 地址解析协议:RAP和RRAP
ARP把IP地址映射为物理地址,RARP把物理地址映射为IP地址。RRAP是被那些没有磁盘驱动器的系统使用(一般是无盘工作站或 X终端),此类应用较少,本章不做讨论。
ARP协议分为ARP请求和ARP应答,源主机发起查询目的MAC地址的报文称为ARP请求,目的主机响应源主机并发送包含本地MAC地址的报文称为ARP应答。
当主机需要找出这个网络中的另一个主机的物理地址时,它就可以发送一个ARP请求报文,这个报文包含了发送方的MAC地址和IP地址以及接收方的IP地址。因为发送方不知道接收方的物理地址,所以这个查询分组会在网络层中进行广播,即ARP请求时发送的接收方物理地址为广播地址,用48’hff_ff_ff_ff_ff_ff表示。ARP请求的示意图如下图所示:
图 28.1.2 ARP请求示意图
上图中的主机A发起ARP请求,由于发送的目的MAC地址为广播地址,所以此时局域网中的所有主机都会进行接收并处理这个ARP请求报文,然后进行验证,查看接收方的IP地址是不是自己的地址。是则返回ARP应答报文,不是则不响应。
只有验证成功的主机才会返回一个ARP应答报文,这个应答报文包含接收方的IP地址和物理地址。ARP应答的示意图如下图所示:
图 28.1.3 ARP应答示意图
主机B利用收到的ARP请求报文中的请求方物理地址,以单播的方式直接发送给主机A,主机A将收到的ARP应答报文中的目的MAC地址解析出来,将目的MAC地址和目的IP地址更新至ARP缓存表中。当再次和主机A通信时,可以直接从ARP缓存表中获取,而不用重新发起ARP请求报文。需要说明的是,ARP缓存表中的表项有过期时间(一般为20分钟),过期之后,需要重新发起ARP请求以获取目的MAC地址。
ARP协议通过以太网进行传输,那么必须也要按照以太网所规定的格式进行传输,我们先来介绍下以太网的帧格式,随后再来向大家详细介绍ARP协议的具体格式。
以太网是目前应用最广泛的局域网通讯方式,同时也是一种协议。以太网协议定义了一系列软件和硬件标准,从而将不同的计算机设备连接在一起。我们知道串口通信单次只传输一个字节,而以太网通信是以数据包的形式传输,其单包数据量达到几十,甚至成百上千个字节。下图为以太网通过ARP传输单包数据的格式,从图中可以看出,以太网的数据包就是对协议的封装来实现数据的传输,即ARP数据位于以太网帧格式的数据段。这里只是让大家了解下以太网数据包的格式,后面会逐个展开来讲。
图 28.1.4 以太网ARP数据包格式
以太网MAC帧格式
以太网技术的正式标准是IEEE 802.3,它规定了以太网传输数据的帧结构,我们可以把以太网MAC层理解成高速公路,我们必须遵循它的规则才能在上面通行,以太网MAC层帧格式如图 28.1.5所示。
图 28.1.5 以太网帧格式
以太网传输数据时按照上面的顺序从头到尾依次被发送和接收,我们下面进一步解释各个区域。
前导码(Preamble):为了实现底层数据的正确阐述,物理层使用7个字节同步码(0和1交替(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-8、CRC-16、CRC-32、CRC-CCIT,其中在网络通信系统中应用最广泛的是CRC-32标准。
在这里还有一个要注意的地方就是以太网相邻两帧之间的时间间隔,即帧间隙(IFG,Interpacket Gap)。帧间隙的时间就是网络设备和组件在接收一帧之后,需要短暂的时间来恢复并为接收下一帧做准备的时间,IFG的最小值是96 bit time,即在媒介中发送96位原始数据所需要的时间,在不同媒介中IFG的最小值是不一样的。不管10M/100M/1000M的以太网,两帧之间最少要有96bit time,IFG的最少间隔时间计算方法如下:
10Mbit/s最小时间为:96100ns = 9600ns;
100Mbit/s最小时间为:9610ns = 960ns;
1000Mbit/s最小时间为:96*1ns = 96ns。
接下来我们介绍ARP协议以及它和以太网MAC层的关系。在介绍ARP协议之前,我们先了解下TCP(传输控制协议)/IP(网际协议)协议簇。TCP/IP是网络使用中最基本的通信协议,虽然从名字看上去TCP/IP包括两个协议,TCP和IP,但TCP/IP实际上是一组协议,它包括上百个各种功能的协议,如:TCP、IP、ARP、UDP等。而TCP协议和IP协议是保证数据完整传输的两个重要的协议,因此TCP/IP协议用来表示Internet协议簇。
TCP/IP协议不仅可以运行在以太网上,也可以运行在FDDI(光纤分布式数据接口)和WLAN(无线局域网)上。反过来,以太网的高层协议不仅可以是TCP/IP协议,也可以是IPX协议(互联网分组交换协议)等,只不过以太网+TCP/IP成为IT行业中应用最普遍的技术。下面我们来熟悉下ARP协议。
ARP协议
ARP协议属于TCP/IP协议簇的一种,从前面介绍的图 28.1.4可以看出,ARP协议位于以太网MAC帧格式的数据段,ARP数据包格式如下图所示。
图 28.1.6 ARP数据包格式
硬件类型(Hardware type):硬件地址的类型,1表示以太网地址。
协议类型(Protocol type):要映射的协议地址类型,ARP协议的上层协议为IP协议,因此该协议类型为IP协议,其值为0x0800。
硬件地址长度(Hardware size):硬件地址(MAC地址)的长度,以字节为单位。对于以太网上IP地址的ARP请求或者应答来说,该值为6。
协议地址长度(Protocol size):IP地址的长度,以字节为单位。对于以太网上IP地址的ARP请求或者应答来说,该值为4。
OP(Opcode):操作码,用于表示该数据包为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.1.7 以太网ARP数据包格式
由上图可知,28字节的ARP数据位于以太网帧格式的数据段。由于以太网数据段最少为46个字节,而ARP数据包总长度为28个字节,因此在ARP数据段后面需要填充18个字节的数据,以满足以太网传输格式的要求。这个填充的过程称为Padding(填充),填充的数据可以为任意值,但一般为0。
RGMII接口介绍
以太网的通信离不开物理层PHY芯片的支持,以太网MAC和PHY之间有一个接口,常用的接口有MII、RMII、GMII、RGMII等。
MII(Medium Independent Interface,媒体独立接口):MII支持10Mbps和100Mbps的操作,数据位宽为4位,在100Mbps传输速率下,时钟频率为25Mhz。
RMII(Reduced MII):RMII是MII的简化版,数据位宽为2位,在100Mbps传输速率下,时钟频率为50Mhz。
GMII(Gigabit MII):GMII接口向下兼容MII接口,支持10Mbps、100Mbps和1000Mbps的操作,数据位宽为8位,在1000Mbps传输速率下,时钟频率为125Mhz。
RGMII(Reduced GMII):RGMII是GMII的简化版,数据位宽为4位,在1000Mbps传输速率下,时钟频率为125Mhz,在时钟的上下沿同时采样数据。在100Mbps和10Mbps通信速率下,为单个时钟沿采样。
在千兆以太网中,常用的接口为RGMII和GMII接口。RGMII接口的优势是同时适用于10M/100M/1000Mbps通信速率,同时占用的引脚数较少。但RGMII接口也有其缺点,就是在PCB布线时需要尽可能对时钟、控制和数据线进行等长处理,且时序约束相对也更为严格。
为了节省引脚,DFZU2EG/4EV MPSoC开发板板载的PHY芯片采用的接口为RGMII接口,下图是MAC侧与PHY侧接口的连接。
图 28.1.8 MAC侧与PHY侧接口连接
ETH_RXC:接收数据参考时钟,1000Mbps速率下,时钟频率为125MHz,时钟为上下沿同时采样;100Mbps速率下,时钟频率为25MHz;10Mbps速率下,时钟频率为2.5MHz,ETH_RXC由PHY侧提供。
ETH_RXCTL(ETH_RX_DV):接收数据控制信号。
ETH_RXD:四位并行的接收数据线。
ETH_TXC:发送参考时钟,1000Mbps速率下,时钟频率为125MHz,时钟为上下沿同时采样;100Mbps速率下,时钟频率为25MHz;10Mbps速率下,时钟频率为2.5MHz,ETH_TXC由MAC侧提供。
ETH_TXCTL(ETH_TXEN):发送数据控制信号。
ETH_TXD:四位并行的发送数据线。
ETH_RESET_N:芯片复位信号,低电平有效。
ETH_MDC:数据管理时钟(Management Data Clock),该引脚对ETH_MDIO信号提供了一个同步的时钟。
ETH_MDIO:数据输入/输出管理(Management Data Input/Output),该引脚提供了一个双向信号用于传递管理信息。
其中ETH_RXC、ETH_RXCTL和ETH_RXD为MAC接收侧引脚;ETH_TXC、ETH_TXCTL和ETH_TXD为MAC发送侧引脚;ETH_MDC和ETH_MDIO为MDIO接口引脚,用于配置PHY芯片内部寄存器;ETH_RST_N为PHY芯片硬件复位信号。由于PHY芯片的内部寄存器在默认配置下也可以正常工作,因此本次实验没有对MDIO接口进行读写操作,只用到了以太网的RGMII接口信号和复位信号。
RGMII使用4bit数据接口,在1000Mbps通信速率下,ETH_TXC和ETH_RXC的时钟频率为125Mhz,采用上下沿DDR(Double Data Rate)的方式在一个时钟周期内传输8位数据信号,即上升沿发送/接收低4位数据,下降沿发送/接收高4位数据。ETH_TXCTL和ETH_RXCTL控制信号同样采用DDR的方式在一个时钟周期内传输两位控制信号,即上升沿发送/接收数据使能(TX_EN/RX_DV)信号,下降沿发送/接收使能信号与错误信号的异或值(TX_ERR xor TX_EN、RX_ERR xor RX_DV)。当RX_DV为高电平(表示数据有效),RX_ERR为低电平(表示数据无错误),则异或的结果值为高电平,因此只有当ETH_RXCTL和ETH_TXCTL信号的上下沿同时为高电平时,发送和接收的数据有效且正确。
当RGMII工作在100Mbps时,ETH_TXC和ETH_RXC的时钟频率为25Mhz,采用上升沿SDR的方式在一个周期内传输4位数据。不过此时ETH_TXCTL和ETH_RXCTL控制信号仍采用上下沿DDR的传输方式。
当RGMII工作在10Mbps时,ETH_TXC和ETH_RXC的时钟频率为2.5Mhz,采用上升沿SDR的方式在一个周期内传输4位数据。ETH_TXCTL和ETH_RXCTL控制信号也采用SDR的传输方式。
RGMII接口时序
PHY芯片的RGMII接口时序,其时钟、控制信号和数据的对齐方式,一般由MDIO接口或者硬件上的特殊引脚进行配置。
RGMII接收端口时钟、控制信号和数据对齐的时序图如下:
图 28.1.9 RGMII接收端口信号对齐
由上图可知,RXC的上下边沿与RXD和RX_CTL信号对齐,相位相同。
RGMII接收端口时钟和控制/数据信号增加延时的时序图如下:
图 28.1.10 RGMII接收信号增加时钟延时
由上图可知,RXC的上下边沿与RXD和RX_CTL信号的中间位置对齐,RXC的时钟周期为8ns,单个高电平或者低电平为4ns,RXC相对于RXD和RX_CTL延时约2ns。
YT8521 RGMII接收端口的信号对齐模式由硬件上的特殊引脚外接上下拉电阻进行配置,如图 28.1.11所示。从下图中可以看出,当管脚RXDLY(RXD0)接上拉电阻时,表示RXC时钟相对于RXD信号,会增加约2ns的延时。而DFZU2EG/4EV MPSoC开发板硬件原理图中YT8521的管脚RXDLY(RXD0)连接的是上拉电阻,因此RXC和RXD之间会有约2ns的延时,RGMII接收端口的时序图如图 28.1.10所示。
图 28.1.12 YT8521 引脚分配
RGMII发送端口正常模式时序图如下:
图 28.1.13 RGMII发送端口正常模式
由上图可知,RGMII发送端口正常模式下,需要满足TXC的上下边沿与TXD和TX_CTL信号对齐,相位相同。YT8521在硬件上面没有做TX端的delay模式,可根据实际情况,选择是否在代码中进行延时(因为一般对端设备的接收端会有延时处理的功能,因此发送端也可以不延时),延时后的时序图如下所示:
图 28.1.14 RGMII发送端口延时模式
由RGMII的接口时序可知,RGMII发送端口在TXC时钟的上升沿传输TXD的低4位和TX_CTL的使能信号;下降沿传输TXD的高4位和TX_CTL的错误信号(实际上是使能信号和错误信号的异或值);RGMII接收端口在RXC时钟的上升沿传输RXD的低4位和RX_CTL的使能信号;下降沿传输RXD的高4位和RX_CTL的错误信号(实际上是使能信号和错误信号的异或值)。
Xilinx原语
原语是Xilinx器件底层硬件中的功能模块,它使用专用的资源来实现一系列的功能。相比于IP核,原语的调用方法更简单,但是一般只用于实现一些简单的功能。本章主要用到了BUFG、BUFIO、IDDRE1、ODDRE1、IDELAYE3和IDELAYCTRL。
BUFG:全局缓冲,BUFG的输出到达FPGA内部的IOB、CLB、块RAM的时钟延迟和抖动最小。BUFG原语模板如下:
BUFG BUFG_inst (
.O(O), // 1-bit output: Clock output
.I(I) // 1-bit input: Clock input
);
除了BUFG外,常用的还有BUFR,BUFR是regional时钟网络,它的驱动范围只能局限在一个clock region的逻辑。BUFR相比BUFG的最大优势是偏斜和功耗都比较小。
BUFIO:BUFIO是IO时钟网络,其独立于全局时钟资源,适合采集源同步数据。它只能驱动IO Block里面的逻辑,不能驱动CLB里面的LUT,REG等逻辑。BUFIO原语模板如下:
BUFIO BUFIO_inst (
.O(O), // 1-bit output: Clock output (connect to I/O clock loads).
.I(I) // 1-bit input: Clock input (connect to an IBUF or BUFMR).
);
BUFIO在采集源同步IO数据时,提供非常小的延时,因此非常适合采集比如RGMII接收侧的数据,但是由于其不能驱动FPGA的内部逻辑,因此需要BUFIO和BUFG配合使用,以达到最佳性能。如ETH_RXC的时钟经过BUFIO,用来采集端口数据;ETH_RXC经过BUFG,用来作为除端口采集外的其他模块的操作时钟。
IDDRE1:在UltraScale系列设备的ILOGIC block中有专属的registers来实现input double-data-rate(IDDRE1) registers,将输入的上下边沿DDR信号,转换成两位单边沿SDR信号。IDDR的原语结构图如下图所示:
图 28.1.15 IDDRE1原语结构图
C:输入的高速时钟;
D:输入的1位DDR数据;
Q1和Q2:分别是“C”时钟上升沿和下降沿同步输出的SDR数据;
CB:高速时钟C的反转;
R:置位/复位信号,高有效;
IDDRE1原语模板如下:
IDDRE1 #(
.DDR_CLK_EDGE("OPPOSITE_EDGE"),
// IDDRE1 mode (OPPOSITE_EDGE, SAME_EDGE, SAME_EDGE_PIPELINED)
.IS_CB_INVERTED(1'b0), // Optional inversion for CB
.IS_C_INVERTED(1'b0) // Optional inversion for C
)
IDDRE1_inst (
.Q1(Q1), // 1-bit output: Registered parallel output 1
.Q2(Q2), // 1-bit output: Registered parallel output 2
.C(C), // 1-bit input: High-speed clock
.CB(CB), // 1-bit input: Inversion of High-speed clock C
.D(D), // 1-bit input: Serial Data Input
.R(R) // 1-bit input: Active High Async Reset
);
DDR_CLK_EDGE参数为IDDRE1的三种采集模式,分别为“OPPOSITE_EDGE”、“SAME_EDGE”和“SAME_EDGE_PIPELINED”模式。
OPPOSITE_EDGE模式的时序图如下图所示:
图 28.1.16 IDDRE1“OPPOSITE_EDGE”模式时序图
OPPOSITE_EDGE模式下,在时钟的上升沿输出的Q1,时钟的下降沿输出Q2。
SAME_EDGE模式的时序图如下图所示:
图 28.1.17 IDDRE1“SAME_EDGE”模式时序图
SAME_EDGE模式下,在时钟的上升沿输出Q1和Q2,但Q1和Q2不在同一个cycle输出。
SAME_EDGE_PIPELINED模式的时序图如下图所示:
图 28.1.18 IDDRE1“SAME_EDGE_PIPELINED”模式时序图
SAME_EDGE_PIPELINED模式下,在时钟的上升沿输出Q1和Q2,Q1和Q2虽然在同一个cycle输出,但整体延时了一个时钟周期。在使用IDDRE1时,一般采用此种模式。
ODDRE1:通过ODDRE1把两路单端的数据合并到一路上输出,上下沿同时输出数据,上升沿输出a路,下降沿输出b路;如果两路输入信号一路固定为1,另外一路固定为0,那么输出的信号实际上是时钟信号。
ODDRE1的原语结构图如下图所示:
图 28.1.19 ODDRE1原语结构图
C:输入的高速时钟;
Q:输出的1位DDR数据;
D1和D2:分别是“C”时钟上升沿和下降沿同步输入的SDR数据。
SR:置位/复位信号,高有效
ODDRE1原语模板如下:
ODDRE1 #(
.IS_C_INVERTED(1'b0), // Optional inversion for C
.IS_D1_INVERTED(1'b0), // Unsupported, do not use
.IS_D2_INVERTED(1'b0), // Unsupported, do not use
.SIM_DEVICE("ULTRASCALE"),
// Set the device version (ULTRASCALE, ULTRASCALE_PLUS, ULTRASCALE_PLUS_ES1,ULTRASCALE_PLUS_ES2, VERSAL, VERSAL_ES1, VERSAL_ES2)
.SRVAL(1'b0)
//Initializes the ODDRE1 Flip-Flops to the specified value (1'b0, 1'b1)
)
ODDRE1_inst (
.Q(Q), // 1-bit output: Data output to IOB
.C(C), // 1-bit input: High-speed clock input
.D1(D1), // 1-bit input: Parallel data input 1
.D2(D2), // 1-bit input: Parallel data input 2
.SR(SR) // 1-bit input: Active High Async Reset
);
SIM_DEVICE参数为ODDRE1的七种设备版本,分别为UltraScale、UltraScale Plus、UltraScale Plus ES1、UltraScale Plus ES2、Versal、Versal ES1、Versal ES2。
28.2实验任务
本节实验任务是使用DFZU2EG/4EV MPSoC开发板上的PL端以太网接口,和上位机实现ARP请求和应答的功能。当上位机发送ARP请求时,开发板返回ARP应答数据。当按下开发板的PL Key1按键时,开发板发送ARP请求,此时上位机返回应答数据。
28.3硬件设计
PL端千兆以太网接口部分的硬件设计原理和“MDIO接口读写测试实验” 完全相同,请参考“MDIO接口读写测试实验”中的硬件设计部分。
本实验中,各端口信号的管脚分配如下表所示:
表 28.3.1 以太网ARP测试实验管脚分配
对应的XDC约束语句如下所示:
#时钟周期约束
create_clock -name sys_clk_p -period 10.000 [get_ports sys_clk_p]
create_clock -period 8.000 -name eth_rxc [get_ports eth_rxc]
#IO管脚约束
set_property IOSTANDARD DIFF_HSTL_I_12 [get_ports sys_clk_p]
set_property IOSTANDARD DIFF_HSTL_I_12 [get_ports sys_clk_n]
set_property PACKAGE_PIN AE5 [get_ports sys_clk_p]
set_property PACKAGE_PIN AF5 [get_ports sys_clk_n]
set_property -dict {PACKAGE_PIN AH11 IOSTANDARD LVCMOS33} [get_ports sys_rst_n]
set_property -dict {PACKAGE_PIN AD11 IOSTANDARD LVCMOS33} [get_ports key]
set_property -dict {PACKAGE_PIN E5 IOSTANDARD LVCMOS18} [get_ports eth_rxc]
set_property -dict {PACKAGE_PIN D5 IOSTANDARD LVCMOS18} [get_ports eth_rx_ctl]
set_property -dict {PACKAGE_PIN G6 IOSTANDARD LVCMOS18} [get_ports {eth_rxd[0]}]
set_property -dict {PACKAGE_PIN F6 IOSTANDARD LVCMOS18} [get_ports {eth_rxd[1]}]
set_property -dict {PACKAGE_PIN G8 IOSTANDARD LVCMOS18} [get_ports {eth_rxd[2]}]
set_property -dict {PACKAGE_PIN F7 IOSTANDARD LVCMOS18} [get_ports {eth_rxd[3]}]
set_property -dict {PACKAGE_PIN D6 IOSTANDARD LVCMOS18} [get_ports eth_txc]
set_property -dict {PACKAGE_PIN F8 IOSTANDARD LVCMOS18} [get_ports eth_tx_ctl]
set_property -dict {PACKAGE_PIN D7 IOSTANDARD LVCMOS18} [get_ports {eth_txd[0]}]
set_property -dict {PACKAGE_PIN E8 IOSTANDARD LVCMOS18} [get_ports {eth_txd[1]}]
set_property -dict {PACKAGE_PIN E9 IOSTANDARD LVCMOS18} [get_ports {eth_txd[2]}]
set_property -dict {PACKAGE_PIN D9 IOSTANDARD LVCMOS18} [get_ports {eth_txd[3]}]
28.4程序设计
根据实验任务,我们可以大致规划出系统的控制流程:首先我们需要完成RGMII接口数据和GMII接口数据的转换,以方便数据的采集和解析,在数据采集过程中所用到的延时原语参考时钟由锁相环输出的时钟提供;其次整个以太网帧格式与ARP协议的实现由ARP顶层模块完成;ARP控制模块负责检测输入的按键是否被按下,控制ARP顶层模块发起请求与产生应答等操作。由此画出系统的功能框图如下图所示:
图 28.4.1 以太网ARP测试系统框图
GMII TO RGMII模块负责将双沿(DDR)数据和单沿(SDR)数据之间的转换;ARP顶层模块实现了以太网ARP数据包的接收、发送以及CRC校验的功能;ARP控制模块根据输入的PL Key1按键信号和接收到的ARP请求信号,控制ARP顶层模块发送ARP请求或者ARP应答。
各模块端口及信号连接如下图所示:
图 28.4.2 顶层模块原理图
由上图可知,FPGA顶层模块例化了以下四个模块,GMII TO RGMII模块(gmii_to_rgmii)、时钟模块、ARP顶层模块(arp)和ARP控制模块(arp_ctrl),实现了各模块之间的数据交互。
其中ARP顶层模块和GMII TO RGMII模块内部也例化了多个其它模块,这样设计的目的是为了方便模块的重用。
顶层模块的代码如下:
1 module eth_arp_test(
2 input sys_clk_p ,
3 input sys_clk_n ,
4 input sys_rst_n , //系统复位信号,低电平有效
5 input key , //PL功能按键1,用于触发开发板发出ARP请求
6 //PL以太网RGMII接口
7 input eth_rxc , //RGMII接收数据时钟
8 input eth_rx_ctl, //RGMII输入数据有效信号
9 input [3:0] eth_rxd , //RGMII输入数据
10 output eth_txc , //RGMII发送数据时钟
11 output eth_tx_ctl, //RGMII输出数据有效信号
12 output [3:0] eth_txd //RGMII输出数据
13 );
14
15 //parameter define
16 //开发板MAC地址 00-11-22-33-44-55
17 parameter BOARD_MAC = 48'h00_11_22_33_44_55;
18 //开发板IP地址 192.168.1.10
19 parameter BOARD_IP = {8'd192,8'd168,8'd1,8'd10};
20 //目的MAC地址 ff_ff_ff_ff_ff_ff
21 parameter DES_MAC = 48'hff_ff_ff_ff_ff_ff;
22 //目的IP地址 192.168.1.102
23 parameter DES_IP = {8'd192,8'd168,8'd1,8'd102};
24
25 //wire define
26 wire gmii_rx_clk; //GMII接收时钟
27 wire gmii_rx_dv ; //GMII接收数据有效信号
28 wire [7:0] gmii_rxd ; //GMII接收数据
29 wire gmii_tx_clk; //GMII发送时钟
30 wire gmii_tx_en ; //GMII发送数据使能信号
31 wire [7:0] gmii_txd ; //GMII发送数据
32
33 wire arp_rx_done; //ARP接收完成信号
34 wire arp_rx_type; //ARP接收类型 0:请求 1:应答
35 wire [47:0] src_mac ; //接收到目的MAC地址
36 wire [31:0] src_ip ; //接收到目的IP地址
37 wire arp_tx_en ; //ARP发送使能信号
38 wire arp_tx_type; //ARP发送类型 0:请求 1:应答
39 wire tx_done ; //发送的目标MAC地址
40 wire [47:0] des_mac ; //发送的目标IP地址
41 wire [31:0] des_ip ; //以太网发送完成信号
42
43 //*****************************************************
44 //** main code
45 //*****************************************************
46
47 assign des_mac = src_mac;
48 assign des_ip = src_ip ;
49 assign eth_txc = clk_125m_deg;
50
51 clk_wiz_0 u_clk_wiz_0
52 (
53 // Clock out ports
54 .clk_out1 (clk_125m_deg ), // output clk_out1
55 // Status and control signals
56 .reset (~sys_rst_n ), // input reset
57 .locked (locked ), // output locked
58 // Clock in ports
59 .clk_in1 (rgmii_txc ) // input clk_in1
60 );
61
62 //GMII接口转RGMII接口
63 gmii_to_rgmii u_gmii_to_rgmii(
64 .gmii_rx_clk (gmii_rx_clk ),
65 .gmii_rx_dv (gmii_rx_dv ),
66 .gmii_rxd (gmii_rxd ),
67 .gmii_tx_clk (gmii_tx_clk ),
68 .gmii_tx_en (gmii_tx_en ),
69 .gmii_txd (gmii_txd ),
70
71 .rgmii_rxc (eth_rxc ),
72 .rgmii_rx_ctl (eth_rx_ctl ),
73 .rgmii_rxd (eth_rxd ),
74 .rgmii_txc (rgmii_txc ),
75 .rgmii_tx_ctl (eth_tx_ctl ),
76 .rgmii_txd (eth_txd )
77 );
78
79 //ARP通信
80 arp
81 #(
82 .BOARD_MAC (BOARD_MAC), //参数例化
83 .BOARD_IP (BOARD_IP ),
84 .DES_MAC (DES_MAC ),
85 .DES_IP (DES_IP )
86 )
87 u_arp(
88 .rst_n (sys_rst_n ),
89
90 .gmii_rx_clk (gmii_rx_clk),
91 .gmii_rx_dv (gmii_rx_dv ),
92 .gmii_rxd (gmii_rxd ),
93 .gmii_tx_clk (gmii_tx_clk),
94 .gmii_tx_en (gmii_tx_en ),
95 .gmii_txd (gmii_txd ),
96
97 .arp_rx_done (arp_rx_done),
98 .arp_rx_type (arp_rx_type),
99 .src_mac (src_mac ),
100 .src_ip (src_ip ),
101 .arp_tx_en (arp_tx_en ),
102 .arp_tx_type (arp_tx_type),
103 .des_mac (des_mac ),
104 .des_ip (des_ip ),
105 .tx_done (tx_done )
106 );
107
108 //ARP控制
109 arp_ctrl u_arp_ctrl(
110 .clk (gmii_rx_clk),
111 .rst_n (sys_rst_n ),
112
113 .key (key ),
114 .arp_rx_done (arp_rx_done),
115 .arp_rx_type (arp_rx_type),
116 .arp_tx_en (arp_tx_en ),
117 .arp_tx_type (arp_tx_type)
118 );
119
120 endmodule
顶层模块主要完成对其余模块的例化。在程序的第15行至第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地址。
程序的第47行和48行代码将收到的对端设备MAC地址和目的IP地址,作为开发板发送时的目的MAC地址和IP地址。
gmii_to_rgmii模块代码如下:
1 module gmii_to_rgmii(
2 //以太网GMII接口
3 output gmii_rx_clk , //GMII接收时钟
4 output gmii_rx_dv , //GMII接收数据有效信号
5 output [7:0] gmii_rxd , //GMII接收数据
6 output gmii_tx_clk , //GMII发送时钟
7 input gmii_tx_en , //GMII发送数据使能信号
8 input [7:0] gmii_txd , //GMII发送数据
9 //以太网RGMII接口
10 input rgmii_rxc , //RGMII接收时钟
11 input rgmii_rx_ctl, //RGMII接收数据控制信号
12 input [3:0] rgmii_rxd , //RGMII接收数据
13 output rgmii_txc , //RGMII发送时钟
14 output rgmii_tx_ctl, //RGMII发送数据控制信号
15 output [3:0] rgmii_txd //RGMII发送数据
16 );
17
18 //*****************************************************
19 //** main code
20 //*****************************************************
21
22 assign gmii_tx_clk = gmii_rx_clk;
23
24 //RGMII接收
25 rgmii_rx u_rgmii_rx(
26 .gmii_rx_clk (gmii_rx_clk ),
27 .rgmii_rxc (rgmii_rxc ),
28 .rgmii_rx_ctl (rgmii_rx_ctl),
29 .rgmii_rxd (rgmii_rxd ),
30
31 .gmii_rx_dv (gmii_rx_dv ),
32 .gmii_rxd (gmii_rxd )
33 );
34
35 //RGMII发送
36 rgmii_tx u_rgmii_tx(
37 .gmii_tx_clk (gmii_tx_clk ),
38 .gmii_tx_en (gmii_tx_en ),
39 .gmii_txd (gmii_txd ),
40
41 .rgmii_txc (rgmii_txc ),
42 .rgmii_tx_ctl (rgmii_tx_ctl),
43 .rgmii_txd (rgmii_txd )
44 );
45
46 endmodule
由该模块的端口可知,该模块实现了双沿(DDR)数据和单沿(SDR)数据之间的转换。程序中第22行将GMII接收时钟赋值给GMII发送时钟,因此GMII的发送时钟和接收时钟实际上为同一个时钟。GMII TO RGMII模块例化了rgmii_rx模块和rgmii_tx模块。
rgmii_rx模块代码如下所示:
1 module rgmii_rx(
2 //以太网RGMII接口
3 input rgmii_rxc , //RGMII接收时钟
4 input rgmii_rx_ctl, //RGMII接收数据控制信号
5 input [3:0] rgmii_rxd , //RGMII接收数据
6
7 //以太网GMII接口
8 output gmii_rx_clk , //GMII接收时钟
9 output gmii_rx_dv , //GMII接收数据有效信号
10 output [7:0] gmii_rxd //GMII接收数据
11 );
12
13 //wire define
14 wire rgmii_rxc_bufg; //全局时钟缓存
15 wire rgmii_rxc_bufio; //全局时钟IO缓存
16 wire [1:0] gmii_rxdv_t; //两位GMII接收有效信号
17
18 //*****************************************************
19 //** main code
20 //*****************************************************
21
22 assign gmii_rx_clk = rgmii_rxc_bufg;
23 assign gmii_rx_dv = gmii_rxdv_t[0] & gmii_rxdv_t[1];
24
25 //全局时钟缓存
26 BUFG BUFG_inst (
27 .I (rgmii_rxc), // 1-bit input: Clock input
28 .O (rgmii_rxc_bufg) // 1-bit output: Clock output
29 );
30
31 //全局时钟IO缓存
32 BUFIO BUFIO_inst (
33 .I (rgmii_rxc), // 1-bit input: Clock input
34 .O (rgmii_rxc_bufio) // 1-bit output: Clock output
35 );
36
37 //将输入的上下边沿DDR信号,转换成两位单边沿SDR信号
38 IDDRE1 #(
39 .DDR_CLK_EDGE ("SAME_EDGE_PIPELINED"),
40 // IDDRE1 mode (OPPOSITE_EDGE, SAME_EDGE, SAME_EDGE_PIPELINED)
41 .IS_CB_INVERTED (1'b0), // Optional inversion for CB
42 .IS_C_INVERTED (1'b0) // Optional inversion for C
43 )
44 IDDRE1_inst (
45 .Q1 (gmii_rxdv_t[0]), // 1-bit output: Registered parallel output 1
46 .Q2 (gmii_rxdv_t[1]), // 1-bit output: Registered parallel output 2
47 .C (rgmii_rxc_bufio), // 1-bit input: High-speed clock
48 .CB (~rgmii_rxc_bufio),// 1-bit input: Inversion of High-speed clock C
49 .D (rgmii_rx_ctl), // 1-bit input: Serial Data Input
50 .R (1'b0) // 1-bit input: Active High Async Reset
51 );
52
53 genvar i;
54 generate for (i=0; i<4; i=i+1)
55 begin : rxdata_bus
56 IDDRE1 #(
57 .DDR_CLK_EDGE ("SAME_EDGE_PIPELINED"),
58 // IDDRE1 mode (OPPOSITE_EDGE, SAME_EDGE, SAME_EDGE_PIPELINED)
59 .IS_CB_INVERTED (1'b0), // Optional inversion for CB
60 .IS_C_INVERTED (1'b0) // Optional inversion for C
61 )
62 IDDRE1_inst (
63 .Q1 (gmii_rxd[i]), // 1-bit output: Registered parallel output 1
64 .Q2 (gmii_rxd[4+i]), // 1-bit output: Registered parallel output 2
65 .C (rgmii_rxc_bufio), // 1-bit input: High-speed clock
66 .CB (~rgmii_rxc_bufio), // 1-bit input: Inversion of High-speed clock C
67 .D (rgmii_rxd[i]), // 1-bit input: Serial Data Input
68 .R (1'b0) // 1-bit input: Active High Async Reset
69 );
70 end
71 endgenerate
72
73 endmodule
该模块通过调用BUFG、BUFIO、IDDRE1原语,实现了RGMII接口输入的DDR数据到SDR数据的转换,输入的rgmii_rx_ctl控制信号的转换方法同样类似。rgmii_rx模块信号转换示意图如下图所示:
图 28.4.3 rgmii_rx模块信号转换示意图
时钟专用引脚输入的rgmii_rxc时钟经过BUFG后,得到gmii_rx_clk,该时钟为全局缓冲时钟,其到达FPGA内部的IOB、CLB、块RAM的时钟延迟和抖动非常小,为其他模块提供操作时钟;另外rgmii_rxc时钟也经过BUFIO,专门用于采集IO端口的数据。
rgmii_rx_ctl控制信号和4位rgmii_rxd数据经过IDDRE1将双沿1位数据转换成单沿两位数据。
另外,在程序的第53行至71行代码通过generate for语句实现对IDDRE1的例化,由于输入的数据引脚为4位数据,因此这里共例化了4次。其等效于分别对输入的IDDRE1例化4次,这里采用generate for的写法可以减少很多的代码量,当需要对某个模块例化较多次数时,这种写法能够大大提高效率。
rgmii_tx模块代码如下所示:
1 module rgmii_tx(
2 //GMII发送端口
3 input gmii_tx_clk , //GMII发送时钟
4 input gmii_tx_en , //GMII输出数据有效信号
5 input [7:0] gmii_txd , //GMII输出数据
6
7 //RGMII发送端口
8 output rgmii_txc , //RGMII发送数据时钟
9 output rgmii_tx_ctl, //RGMII输出数据有效信号
10 output [3:0] rgmii_txd //RGMII输出数据
11 );
12
13 //*****************************************************
14 //** main code
15 //*****************************************************
16
17 assign rgmii_txc = gmii_tx_clk;
18
19 //输出双沿采样寄存器 (rgmii_tx_ctl)
20 ODDRE1 #(
21 .IS_C_INVERTED (1'b0), // Optional inversion for C
22 .IS_D1_INVERTED (1'b0), // Unsupported, do not use
23 .IS_D2_INVERTED (1'b0), // Unsupported, do not use
24 .SIM_DEVICE ("ULTRASCALE"),
25 // Set the device version (ULTRASCALE, ULTRASCALE_PLUS,
26 // ULTRASCALE_PLUS_ES1,ULTRASCALE_PLUS_ES2)
27 .SRVAL(1'b0)
28 // Initializes the ODDRE1 Flip-Flops to the specified value (1'b0, 1'b1)
29 )
30 ODDRE1_tx_ctl (
31 .Q (rgmii_tx_ctl), // 1-bit output: Data output to IOB
32 .C (gmii_tx_clk), // 1-bit input: High-speed clock input
33 .D1 (gmii_tx_en), // 1-bit input: Parallel data input 1
34 .D2 (gmii_tx_en), // 1-bit input: Parallel data input 2
35 .SR (1'b0) // 1-bit input: Active High Async Reset
36 );
37
38 genvar i;
39 generate for (i=0; i<4; i=i+1)
40 begin : txdata_bus
41 ODDRE1 #(
42 .IS_C_INVERTED(1'b0), // Optional inversion for C
43 .IS_D1_INVERTED(1'b0), // Unsupported, do not use
44 .IS_D2_INVERTED(1'b0), // Unsupported, do not use
45 .SIM_DEVICE("ULTRASCALE"),
46 // Set the device version (ULTRASCALE, ULTRASCALE_PLUS,
47 // ULTRASCALE_PLUS_ES1,ULTRASCALE_PLUS_ES2)
48 .SRVAL(1'b0)
49 // Initializes the ODDRE1 Flip-Flops to the specified value (1'b0, 1'b1)
50 )
51 ODDRE1_inst (
52 .Q (rgmii_txd[i]), // 1-bit output: Data output to IOB
53 .C (gmii_tx_clk), // 1-bit input: High-speed clock input
54 .D1 (gmii_txd[i]), // 1-bit input: Parallel data input 1
55 .D2 (gmii_txd[4+i]), // 1-bit input: Parallel data input 2
56 .SR (1'b0) // 1-bit input: Active High Async Reset
57 );
58 end
59 endgenerate
60
61 endmodule
该模块通过调用ODDRE1原语将输入的单沿8位数据(gmii_txd)转换成双沿采样的4位数据(rgmii_txd),gmii_tx_en和rgmii_tx_ctl信号的处理方法同样类似。rgmii_tx模块信号转换示意图如下图所示:
图 28.4.4 rgmii_tx模块信号转换示意图
gmii_tx_en数据使能信号和8位gmii_txd数据经过ODDRE1将单沿2位数据转换成双沿1位数据。需要说明的是,在程序的第38行至第59行同样通过generate for语句实现原语的多次例化。
ARP顶层模块实现了整个以太网帧格式与ARP协议的功能,其模块端口及信号连接如下图所示:
图 28.4.5 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_mac和src_ip分别是解析出的对端设备MAC地址和IP地址。
ARP发送模块(arp_tx):ARP发送模块根据以太网帧格式和ARP协议发送ARP请求或者ARP应答数据。arp_tx_en和arp_tx_type分别表示ARP发送模块的使能信号和发送ARP类型。dec_mac和dec_ip分别设置对端设备MAC地址和IP地址。
CRC校验模块(crc32_d8):CRC校验模块是对ARP发送模块的数据(不包括前导码和帧起始界定符)做校验,把校验结果值拼在以太网帧格式的FCS字段,如果CRC校验值计算错误或者没有的话,那么电脑网卡会直接丢弃该帧导致收不到数据(有些网卡是可以设置不做校验的)。CRC32校验在FPGA实现的原理是LFSR(Linear Feedback Shift Register,线性反馈移位寄存器),其思想是各个寄存器储存着上一次CRC32运算的结果,寄存器的输出即为CRC32的值。需要说明的是,本次实验只对发送模块做校验,没有对接收模块做校验。这是由于我们可以直接通过解析出的数据来大致判断接收是否正确,而发送模块必须发送正确的校验数据,否则发送的数据直接被电脑的网卡丢弃,导致ARP请求或者应答失败。
在简介部分我们向大家介绍过,ARP的数据包格式包括前导码+SFD、以太网帧头、ARP数据(包括填充部分数据)和CRC校验。在接收以太网数据的过程中,这些不同部分的数据可以刚好对应状态机的不同状态位,因此我们可以通过状态机来解析以太网的数据。
ARP接收模块通过状态机来解析数据,其状态跳转图如下图所示:
图 28.4.6 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数据状态和接收结束状态源代码,代码如下:
186 st_arp_data : begin
187 if(gmii_rx_dv) begin
188 cnt <= cnt + 5'd1;
189 if(cnt == 5'd6)
190 op_data[15:8] <= gmii_rxd; //操作码
191 else if(cnt == 5'd7)
192 op_data[7:0] <= gmii_rxd;
193 else if(cnt >= 5'd8 && cnt < 5'd14) //源MAC地址
194 src_mac_t <= {src_mac_t[39:0],gmii_rxd};
195 else if(cnt >= 5'd14 && cnt < 5'd18) //源IP地址
196 src_ip_t<= {src_ip_t[23:0],gmii_rxd};
197 else if(cnt >= 5'd24 && cnt < 5'd28) //目标IP地址
198 des_ip_t <= {des_ip_t[23:0],gmii_rxd};
199 else if(cnt == 5'd28) begin
200 cnt <= 5'd0;
201 if(des_ip_t == BOARD_IP) begin //判断目的IP地址和操作码
202 if((op_data == 16'd1) || (op_data == 16'd2)) begin
203 skip_en <= 1'b1;
204 rx_done_t <= 1'b1;
205 src_mac <= src_mac_t;
206 src_ip <= src_ip_t;
207 src_mac_t <= 48'd0;
208 src_ip_t <= 32'd0;
209 des_mac_t <= 48'd0;
210 des_ip_t <= 32'd0;
211 if(op_data == 16'd1)
212 arp_rx_type <= 1'b0; //ARP请求
213 else
214 arp_rx_type <= 1'b1; //ARP应答
215 end
216 else
217 error_en <= 1'b1;
218 end
219 else
220 error_en <= 1'b1;
221 end
222 end
223 end
224 st_rx_end : begin
225 cnt <= 5'd0;
226 //单包数据接收完成
227 if(gmii_rx_dv == 1'b0 && skip_en == 1'b0)
228 skip_en <= 1'b1;
229 end
st_arp_data状态根据ARP协议解析数据,在程序的第201行至第202行代码判断目的IP地址和OP操作码是否正确,如果错误,则丢弃该包数据。在程序的第211行至第214行代码根据操作码为arp_rx_type(接收到的ARP数据包类型)赋值,当接收到ARP请求包时,arp_rx_type等于0;当接收到ARP应答包时,arp_rx_type等于1。
ARP接收过程中采集的ILA波形图如图 28.4.7所示,gmii_rx_dv信号拉高表示此时输入的数据有效,根据gmii_rxd的值来解析数据。从图中可以看出,发送端的MAC地址和地址,以及当前接收到的以太网数据包类型为ARP(0x0806)。在接收完ARP数据包之后,拉高arp_rx_done信号表示接收完成,图中arp_rx_type信号为低电平,表示当前接收到的是ARP请求数据包。
图 28.4.7 ARP接收采集ILA波形图
ARP发送模块则是根据以太网帧格式是ARP协议发送数据,也就是接收模块的逆过程,同样也非常适合使用状态机来完成发送数据的功能,状态跳转图如下图所示:
图 28.4.8 ARP发送模块状态跳转图
发送模块和接收模块有很多相似之处,同样使用三段式状态机来发送以太网包,从上图可以比较直观的看到每个状态实现的功能以及跳转到下一个状态的条件。
发送模块的代码中定义了数组来存储前导码+帧头、以太网的帧头、ARP数据,在复位时初始化数组的值,部分源代码如下。
66 reg [7:0] preamble[7:0] ; //前导码+SFD
67 reg [7:0] eth_head[13:0]; //以太网首部
68 reg [7:0] arp_data[27:0]; //ARP数据
省略部分代码……
155 //初始化数组
156 //前导码 7个8'h55 + 1个8'hd5
157 preamble[0] <= 8'h55;
158 preamble[1] <= 8'h55;
159 preamble[2] <= 8'h55;
160 preamble[3] <= 8'h55;
161 preamble[4] <= 8'h55;
162 preamble[5] <= 8'h55;
163 preamble[6] <= 8'h55;
164 preamble[7] <= 8'hd5;
165 //以太网帧头
166 eth_head[0] <= DES_MAC[47:40]; //目的MAC地址
167 eth_head[1] <= DES_MAC[39:32];
168 eth_head[2] <= DES_MAC[31:24];
169 eth_head[3] <= DES_MAC[23:16];
170 eth_head[4] <= DES_MAC[15:8];
171 eth_head[5] <= DES_MAC[7:0];
172 eth_head[6] <= BOARD_MAC[47:40]; //源MAC地址
173 eth_head[7] <= BOARD_MAC[39:32];
174 eth_head[8] <= BOARD_MAC[31:24];
175 eth_head[9] <= BOARD_MAC[23:16];
176 eth_head[10] <= BOARD_MAC[15:8];
177 eth_head[11] <= BOARD_MAC[7:0];
178 eth_head[12] <= ETH_TYPE[15:8]; //以太网帧类型
179 eth_head[13] <= ETH_TYPE[7:0];
180 //ARP数据
181 arp_data[0] <= HD_TYPE[15:8]; //硬件类型
182 arp_data[1] <= HD_TYPE[7:0];
183 arp_data[2] <= PROTOCOL_TYPE[15:8]; //上层协议类型
184 arp_data[3] <= PROTOCOL_TYPE[7:0];
185 arp_data[4] <= 8'h06; //硬件地址长度,6
186 arp_data[5] <= 8'h04; //协议地址长度,4
187 arp_data[6] <= 8'h00; //OP,操作码 8'h01:ARP请求 8'h02:ARP应答
188 arp_data[7] <= 8'h01;
189 arp_data[8] <= BOARD_MAC[47:40]; //发送端(源)MAC地址
190 arp_data[9] <= BOARD_MAC[39:32];
191 arp_data[10] <= BOARD_MAC[31:24];
192 arp_data[11] <= BOARD_MAC[23:16];
193 arp_data[12] <= BOARD_MAC[15:8];
194 arp_data[13] <= BOARD_MAC[7:0];
195 arp_data[14] <= BOARD_IP[31:24]; //发送端(源)IP地址
196 arp_data[15] <= BOARD_IP[23:16];
197 arp_data[16] <= BOARD_IP[15:8];
198 arp_data[17] <= BOARD_IP[7:0];
199 arp_data[18] <= DES_MAC[47:40]; //接收端(目的)MAC地址
200 arp_data[19] <= DES_MAC[39:32];
201 arp_data[20] <= DES_MAC[31:24];
202 arp_data[21] <= DES_MAC[23:16];
203 arp_data[22] <= DES_MAC[15:8];
204 arp_data[23] <= DES_MAC[7:0];
205 arp_data[24] <= DES_IP[31:24]; //接收端(目的)IP地址
206 arp_data[25] <= DES_IP[23:16];
207 arp_data[26] <= DES_IP[15:8];
208 arp_data[27] <= DES_IP[7:0];
以上代码在复位时对数组进行初始化。
216 st_idle : begin
217 if(pos_tx_en) begin
218 skip_en <= 1'b1;
219 //如果目标MAC地址和IP地址已经更新,则发送正确的地址
220 if((des_mac != 48'b0) || (des_ip != 32'd0)) begin
221 eth_head[0] <= des_mac[47:40];
222 eth_head[1] <= des_mac[39:32];
223 eth_head[2] <= des_mac[31:24];
224 eth_head[3] <= des_mac[23:16];
225 eth_head[4] <= des_mac[15:8];
226 eth_head[5] <= des_mac[7:0];
227 arp_data[18] <= des_mac[47:40];
228 arp_data[19] <= des_mac[39:32];
229 arp_data[20] <= des_mac[31:24];
230 arp_data[21] <= des_mac[23:16];
231 arp_data[22] <= des_mac[15:8];
232 arp_data[23] <= des_mac[7:0];
233 arp_data[24] <= des_ip[31:24];
234 arp_data[25] <= des_ip[23:16];
235 arp_data[26] <= des_ip[15:8];
236 arp_data[27] <= des_ip[7:0];
237 end
238 if(arp_tx_type == 1'b0)
239 arp_data[7] <= 8'h01; //ARP请求
240 else
241 arp_data[7] <= 8'h02; //ARP应答
242 end
243 end
在程序的第220行至241行代码,根据输入的发送类型、目的MAC地址和IP地址,重新更新数组里的值。
265 st_arp_data : begin //发送ARP数据
266 crc_en <= 1'b1;
267 gmii_tx_en <= 1'b1;
268 //至少发送46个字节
269 if (cnt == MIN_DATA_NUM - 1'b1) begin
270 skip_en <= 1'b1;
271 cnt <= 1'b0;
272 data_cnt <= 1'b0;
273 end
274 else
275 cnt <= cnt + 1'b1;
276 if(data_cnt <= 6'd27) begin
277 data_cnt <= data_cnt + 1'b1;
278 gmii_txd <= arp_data[data_cnt];
279 end
280 else
281 gmii_txd <= 8'd0; //Padding,填充0
282 end
程序第265行至第282行代码为发送ARP数据的状态。我们前面讲过以太网帧格式的数据部分最少是46个字节,ARP数据只有28个字节,因此在发送完ARP数据之后补充发送18个字节,填充的数据为0。
283 st_crc : begin //发送CRC校验值
284 gmii_tx_en <= 1'b1;
285 cnt <= cnt + 1'b1;
286 if(cnt == 6'd0)
287 gmii_txd <= {~crc_next[0], ~crc_next[1], ~crc_next[2],~crc_next[3],
288 ~crc_next[4], ~crc_next[5], ~crc_next[6],~crc_next[7]};
289 else if(cnt == 6'd1)
290 gmii_txd <= {~crc_data[16], ~crc_data[17], ~crc_data[18],
291 ~crc_data[19], ~crc_data[20], ~crc_data[21],
292 ~crc_data[22],~crc_data[23]};
293 else if(cnt == 6'd2) begin
294 gmii_txd <= {~crc_data[8], ~crc_data[9], ~crc_data[10],
295 ~crc_data[11],~crc_data[12], ~crc_data[13],
296 ~crc_data[14],~crc_data[15]};
297 end
298 else if(cnt == 6'd3) begin
299 gmii_txd <= {~crc_data[0], ~crc_data[1], ~crc_data[2],~crc_data[3],
300 ~crc_data[4], ~crc_data[5], ~crc_data[6],~crc_data[7]};
301 tx_done_t <= 1'b1;
302 skip_en <= 1'b1;
303 cnt <= 1'b0;
304 end
305 end
程序的第283行至305行代码为发送CRC校验值状态,发送模块的CRC校验是由crc32_d8模块完成的,发送模块将输入的crc的计算结果每4位高低位互换,按位取反发送出去,crc计算部分在后面阐述。
ARP发送过程中采集的ILA波形图如图 28.4.9所示,arp_tx_en信号作为开始发送ARP数据包的触发信号,arp_tx_type为高电平,表示发送ARP应答数据包。ARP应答数据包中的目的MAC地址和目的IP地址从ARP接收数据包中获取,gmii_tx_en拉高,表示gmii_txd数据有效,在发送完ARP数据包后,输出一个脉冲信号(tx_done),表示发送完成。
图 28.4.9 ARP发送采集的ILA波形图
CRC校验模块主要完成对ARP发送模块数据的校验,由于代码较长,这里不再贴出代码。CRC32校验在FPGA实现的原理是线性反馈移位寄存器,其思想是各个寄存器储存着上一次CRC32运算的结果,寄存器的输出即为CRC32的值。CRC32的原理与公式推导较复杂,只需稍作修改就可以直接使用,在此可不比深究。
ARP控制模块的代码如下:
1 module arp_ctrl(
2 input clk , //输入时钟
3 input rst_n , //复位信号,低电平有效
4
5 input key , //PL功能按键1,用于触发开发板发出ARP请求
6 input arp_rx_done, //ARP接收完成信号
7 input arp_rx_type, //ARP接收类型 0:请求 1:应答
8 output reg arp_tx_en , //ARP发送使能信号
9 output reg arp_tx_type //ARP发送类型 0:请求 1:应答
10 );
11
12 //reg define
13 reg key_d0;
14 reg key_d1;
15
16 //wire define
17 wire pos_key; //key信号上升沿
18
19 //*****************************************************
20 //** main code
21 //*****************************************************
22
23 assign pos_key = ~key_d1 & key_d0;
24
25 //对arp_tx_en信号延时打拍两次,用于采key的上升沿
26 always @(posedge clk or negedge rst_n) begin
27 if(!rst_n) begin
28 key_d0 <= 1'b0;
29 key_d1 <= 1'b0;
30 end
31 else begin
32 key_d0 <= key;
33 key_d1 <= key_d0;
34 end
35 end
36
37 //为arp_tx_en和arp_tx_type赋值
38 always @(posedge clk or negedge rst_n) begin
39 if(!rst_n) begin
40 arp_tx_en <= 1'b0;
41 arp_tx_type <= 1'b0;
42 end
43 else begin
44 if(pos_key == 1'b1) begin //检测到输入按键上升沿
45 arp_tx_en <= 1'b1;
46 arp_tx_type <= 1'b0;
47 end
48 //接收到ARP请求,开始控制ARP发送模块应答
49 else if((arp_rx_done == 1'b1) && (arp_rx_type == 1'b0)) begin
50 arp_tx_en <= 1'b1;
51 arp_tx_type <= 1'b1;
52 end
53 else
54 arp_tx_en <= 1'b0;
55 end
56 end
57
58 endmodule
ARP控制模块的代码较简单,首先检测输入PL Key1按键的上升沿,当检测到上升沿之后,触发ARP顶层模块发起ARP请求信号;同时检测输入的arp_rx_done和arp_rx_type信号,当接收上位机的ARP请求信号后,触发ARP顶层模块发送ARP应答信号,将开发板的MAC地址发送给上位机。
28.5下载验证
将下载器一端连接电脑,另一端与开发板上的JTAG下载口连接,将网线一端连接开发板的PL网口(PL_ETH),另一端连接电脑的网口,最后连接电源线后拨动开关按键给开发板上电。PL_ETH网口的位置如下图所示。
图 28.5.1 PL_ETH网口位置
点击Vivado左侧“Flow Navigator”窗口最下面的“Open Hardware Manager”,此时Vivado软件识别到下载器,点击“Hardware”窗口中“Program Device”下载程序,在弹出的界面中选择“Program”下载程序。
程序下载完成后,PHY芯片会和电脑网卡进行通信(自协商),如果程序下载正确并且硬件连接无误的话,我们点击电脑右下角的网络图标,会看到本地连接刚开始显示的是正在识别,一段时间之后显示未识别的网络,打开方式如下图所示(WIN7和WIN10操作可能存在差异,但基本相同)。
图 28.5.2 点击网络图标
点击图 27.5.3中的“未识别的网络(无Internet)”,弹出如下图所示界面。
图 28.5.3 网络设置界面
点击“更改适配器”选项,弹出如下图所示界面。
图 28.5.4 “网络适配器界面”
如果看到上图“以太网”显示未识别的网络之后,说明硬件连接和程序都是没有问题的,接下来设置以太网的IP地址,改成代码中设置的目的IP地址,顶层模块参数定义如下:
//目的IP地址 192.168.1.102
parameter DES_IP = {8’d192,8’d168,8’d1,8’d102};
因此接下来将电脑以太网的IP地址设置成192.168.1.102。鼠标右击图 27.5.5中的以太网,如下图所示:
图 28.5.5 鼠标右击“以太网”
点击“属性”,弹出如下图所示界面。
图 28.5.6 以太网“属性”界面
鼠标双击“Internet协议版本4(TCP/IPv4)”,弹出如下图所示界面。
图 28.5.7 设置以太网IP地址
在“Internet协议版本4(TCP/IPv4)”属性界面中,选择使用下面的IP地址,IP地址设置成192.168.1.102,并点击确定完成设置。
接下来以管理员身份打开电脑的命令的DOS命令窗口(注意必须以管理员身份打开),打开方式如下:
图 28.5.8 打开电脑DOS命令窗口
打开DOS命令窗口后,在命令行中输入“arp -a”,如下图所示:
图 28.5.9 输入命令“arp -a”
输入完成后,按下键盘的回车键,此时会弹出电脑中所有网络接口的ARP缓存表,我们只需要关注以太网接口的ARP缓存表(IP地址为192.168.1.102),如下图所示:
图 28.5.10 以太网接口ARP缓存表
可以发现,此时ARP缓存表中还没有开发板的MAC地址和IP地址,此时我们按下开发板的PL Key1按键。按下后,开发板会向电脑发起ARP请求,并且电脑会返回自己的MAC地址到开发板。
需要说明的是,在开发板发起ARP请求时,会将开发板的MAC地址和IP地址都发给电脑,此时电脑就已经获取到了开发板的MAC地址和IP地址,并更新至ARP的缓存表中,我们重新在DOS命令中输入“arp -a”,如图 28.5.11和图 28.5.12所示。
图 28.5.12 开发板的MAC地址更新至缓存表中
由上图可知,此时以太网接口的ARP缓存表中已经添加了开发板的IP地址(192.168.1.10)和MAC地址(00-11-22-33-44-55),说明开发板发送ARP请求成功。如果大家操作失败,请检查开发板的PL网口(GE_PL)是否通过网线连接电脑的网口,并且此时开发板已经下载程序以及通过按下PL Key1按键来触发ARP请求。另外,如果电脑网口不支持千兆网通信,那么也会导致ARP操作失败。
接下来我们再来通过电脑发起ARP请求,验证开发板有没有正确返回ARP应答。我们先从以太网ARP缓存表中删除开发板的MAC地址,删除方法是在DOS命令中输入“arp -d”,并按下按键的回车键,如下图所示:
图 28.5.13 输入“arp -d”
接下来重新在DOS命令中输入“arp -a”,来验证是否删除成功(如果没有以管理员运行会导致删除失败),输入完成后,如下图所示;
图 28.5.14 删除开发板MAC地址
此时我们之前获取的开发板MAC地址已经删除成功,接下来在DOS命令中输入“ping 192.168.1.10”,来让电脑发起ARP请求,如下图所示。
图 28.5.15 电脑发起“ARP”请求
需要说明的是,ping是一个十分强大的TCP/IP工具,它可以用来检测网络的连通情况和分析网络速度。ping命令是一个固定格式的ICMP(Internet控制报文协议)请求数据包,之后会发起ARP请求命令,所以我们这里是通过ping命令来间接发起ARP请求。由于开发板并没有实现ICMP协议,因此在ping时会请求超时,但是在ping的过程中发起的ARP请求,开发板会响应并返回ARP应答数据。
接下来再次在DOS命令中输入“arp -a”,查询是否成功获取到开发板MAC地址,如下图所示:
图 28.5.16 开发板MAC地址获取成功
由上图可知,电脑正确获取到开发板的MAC地址,并更新至ARP缓存表中。到这里,开发板实现的ARP协议就已经全部验证成功了。
接下来介绍一个以太网通信时经常使用的抓包软件,该软件位于开发板所随附的资料“6_软件资料/1_软件/Wireshark”目录下,也可以直接在网上搜索下载,我们现在打开Wireshark,界面如下图所示:
图 28.5.17 wireshark打开界面
双击上图所示的以太网或者先选中以太网,再点击上方红框选中的蓝色按钮,即可开始抓取本地连接的数据包,抓取界面如下图所示:
图 28.5.18 wireshark以太网打开界面
从上图可以看到,已经抓取到其它应用程序使用以太网发送的数据包,但是这些数据包并不是开发板发送的数据包,我们这个时候按下开发板的PL Key1按键,就可以在wireshark中抓取到数据包了,抓取到的数据包如下图所示:
图 28.5.19 wireshark抓取到的数据包
上图中第38行数据包是开发板发送给电脑的ARP请求包,第39行数据包是电脑发送给开发板的ARP应答包,此时双击第38行即可看到开发板发送的详细数据,如下图所示:
图 28.5.20 wireshark抓取到的详细数据
上图中下方红框为开发板发送的16进制数据(去掉前导码、SFD和CRC值),可以看到,后面的18个0就是我们在发送时填充的18个字节数据。需要说明的是,当打开第39行电脑返回的ARP请求包时,看不到填充的0,这是由于后面填充的数据是网卡自动填充的,因此wireshark中会看不到。