本帖最后由 正点原子01 于 2020-8-3 09:57 编辑
1)实验平台:正点原子领航者ZYNQ开发板
2)平台购买地址:https://item.taobao.com/item.htm?&id=606160108761
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-301505-1-1.html
4)对正点原子FPGA感兴趣的同学可以加群讨论:712557122
5)关注正点原子公众号,获取最新资料更新
第二十五章 以太网ARP测试实验
在以太网中,一个主机和另一个主机进行通信,必须要知道目的主机的MAC地址(物理地址),而目的MAC地址的获取由ARP协议完成。本章我们来学习如何通过领航者ZYNQ开发板实现ARP协议的功能。 本章分为以下几个章节:
1.1 简介 1.2 实验任务 1.3 硬件设计 1.4 程序设计 1.5 下载验证
1.1 简介ARP概述 ARP(AddressResolution 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(逆地址解析协议)两种,如下图所示。 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请求的示意图如下图所示: 上图中的主机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数据位于以太网帧格式的数据段。这里只是让大家了解下以太网数据包的格式,后面会逐个展开来讲。 以太网MAC帧格式 以太网技术的正式标准是IEEE802.3,它规定了以太网传输数据的帧结构,我们可以把以太网MAC层理解成高速公路,我们必须遵循它的规则才能在上面通行,以太网MAC层帧格式如图25.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,MaximumTransmission Unit),之所以限制最大传输单元是因为在多个计算机的数据帧排队等待传输时,如果某个数据帧太大的话,那么其它数据帧等待的时间就会加长,导致体验变差,这就像一个十字路口的红绿灯,你可以让绿灯持续亮一小时,但是等红灯的人一定不愿意的。另外还要考虑网络I/O控制器缓存区资源以及网络最大的承载能力等因素,因此最大传输单元是由各种综合因素决定的。为了避免增加额外的配置,通常以太网的有效数据字段小于1500个字节。 帧检验序列(FCS,Frame CheckSequence):为了确保数据的正确传输,在数据的尾部加入了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最小时间为:96*100ns= 9600ns; 100Mbit/s最小时间为:96*10ns= 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协议簇的一种,从前面介绍的图25.1.4可以看出,ARP协议位于以太网MAC帧格式的数据段,ARP数据包格式如下图所示。 硬件类型(Hardwaretype):硬件地址的类型,1表示以太网地址。 协议类型(Protocoltype):要映射的协议地址类型,ARP协议的上层协议为IP协议,因此该协议类型为IP协议,其值为0x0800。 硬件地址长度(Hardwaresize):硬件地址(MAC地址)的长度,以字节为单位。对于以太网上IP地址的ARP请求或者应答来说,该值为6。 协议地址长度(Protocolsize):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字节的ARP数据位于以太网帧格式的数据段。由于以太网数据段最少为46个字节,而ARP数据包总长度为28个字节,因此在ARP数据段后面需要填充18个字节的数据,以满足以太网传输格式的要求。这个填充的过程称为Padding(填充),填充的数据可以为任意值,但一般为0。 RGMII接口介绍 以太网的通信离不开物理层PHY芯片的支持,以太网MAC和PHY之间有一个接口,常用的接口有MII、RMII、GMII、RGMII等。 MII(MediumIndependent 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布线时需要尽可能对时钟、控制和数据线进行等长处理,且时序约束相对也更为严格。 为了节省引脚,领航者ZYNQ开发板板载的PHY芯片采用的接口为RGMII接口,下图是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:数据管理时钟(ManagementData Clock),该引脚对ETH_MDIO信号提供了一个同步的时钟。 ETH_MDIO:数据输入/输出管理(ManagementData 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 DataRate)的方式在一个时钟周期内传输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发送端口正常模式时序图如下: 由上图可知,RGMII发送端口正常模式下,需要满足TXC的上下边沿与TXD和TX_CTL信号的中间位置对齐,TXC的时钟周期为8ns,单个高电平或者低电平为4ns,TXC相对于TXD和TX_CTL延时约2ns。 RGMII发送端口延时模式时序图如下: 由上图可知,RGMII发送端口延时模式下,需要满足TXC的上下边沿与TXD和TX_CTL信号对齐,相位相同。B50610 RGMII发送端口默认采用延时模式,其模式由MDIO接口进行配置,如下图25.1.11所示。其中寄存器地址0x1C的Bit[9]用于配置发送端口的延时模式,默认配置为1,即采用延时模式。 RGMII接收端口正常模式时序图如下: 图25.1.12 RGMII接收端口正常模式 RGMII接收端口正常模式下,RXC的上下边沿与RXD和RX_CTL信号对齐,相位相同。 RGMII接收端口延时模式时序图如下: RGMII接收端口延时模式下,RXC的上下边沿与RXD和RX_CTL信号的中间位置对齐,RXC的时钟周期为8ns,单个高电平或者低电平为4ns,RXC相对于RXD和RX_CTL延时约2ns。B50610 RGMII接收端口默认采用延时模式,其模式由MDIO接口进行配置,如下图25.1.14所示。其中寄存器地址0x18的Bit[8]用于配置发送端口的延时模式,默认配置为1,即采用延时模式。 由RGMII的接口时序可知,RGMII发送端口在TXC时钟的上升沿传输TXD的高4位和TX_CTL的使能信号;下降沿传输TXD的低4位和TX_CTL的错误信号(实际上是使能信号和错误信号的异或值);RGMII接收端口在RXC时钟的上升沿传输RXD的高4位和RX_CTL的使能信号;下降沿传输RXD的低4位和RX_CTL的错误信号(实际上是使能信号和错误信号的异或值)。因此为了便于正确接收数据以及解析数据,需要将这种双沿采集4位数据的端口转换成常规的单沿采集8位数据的端口,即RGMII接口到GMII接口的转换。这个需要借助于Xilinx提供的原语进行转换,接下来我们介绍下本次实验所用到的Xilinx的原语。 Xilinx原语 原语是Xilinx器件底层硬件中的功能模块,它使用专用的资源来实现一系列的功能。相比于IP核,原语的调用方法更简单,但是一般只用于实现一些简单的功能。本章主要用到了BUFG、BUFIO、IDDR、ODDR、IDELAYE2和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,用来作为除端口采集外的其他模块的操作时钟。 IDDR:在7系列设备的ILOGICblock中有专属的registers来实现input double-data-rate(IDDR) registers,将输入的上下边沿DDR信号,转换成两位单边沿 SDR信号。IDDR的原语结构图如下图所示: C:输入的同步时钟; D:输入的1位DDR数据; Q1和Q2:分别是“C”时钟上升沿和下降沿同步输出的SDR数据。 CE:时钟使能信号; S/R:置位/复位信号,这两个信号不能同时拉高。 IDDR原语模板如下: - IDDR #(
- .DDR_CLK_EDGE("OPPOSITE_EDGE"),// "OPPOSITE_EDGE", "SAME_EDGE"
- // or "SAME_EDGE_PIPELINED"
- .INIT_Q1(1'b0),// Initial value of Q1: 1'b0 or 1'b1
- .INIT_Q2(1'b0),// Initial value of Q2: 1'b0 or 1'b1
- .SRTYPE("SYNC")// Set/Reset type: "SYNC" or "ASYNC"
- ) IDDR_inst (
- .Q1(Q1),// 1-bit output forpositive edge of clock
- .Q2(Q2),// 1-bit output fornegative edge of clock
- .C(C),// 1-bit clock input
- .CE(CE),// 1-bit clock enableinput
- .D(D),// 1-bit DDR data input
- .R(R),// 1-bit reset
- .S(S)// 1-bit set
- );
复制代码 DDR_CLK_EDGE参数为IDDR的三种采集模式,分别为“OPPOSITE_EDGE”、“SAME_EDGE”和“SAME_EDGE_PIPELINED”模式。 OPPOSITE_EDGE模式的时序图如下图所示: 图25.1.16 IDDR“OPPOSITE_EDGE”模式时序图 OPPOSITE_EDGE模式下,在时钟的上升沿输出的Q1,时钟的下降沿输出Q2。 SAME_EDGE模式的时序图如下图所示: 图25.1.17 IDDR“SAME_EDGE”模式时序图 SAME_EDGE模式下,在时钟的上升沿输出Q1和Q2,但Q1和Q2不在同一个cycle输出。 SAME_EDGE_PIPELINED模式的时序图如下图所示: 图25.1.18 IDDR“SAME_EDGE_PIPELINED”模式时序图 SAME_EDGE_PIPELINED模式下,在时钟的上升沿输出Q1和Q2,Q1和Q2虽然在同一个cycle输出,但整体延时了一个时钟周期。在使用IDDR时,一般采用此种模式。 ODDR:通过ODDR把两路单端的数据合并到一路上输出,上下沿同时输出数据,上升沿输出a路,下降沿输出b路;如果两路输入信号一路固定为1,另外一路固定为0,那么输出的信号实际上是时钟信号。 ODDR的原语结构图如下图所示: C:输入的同步时钟; Q:输出的1位DDR数据; D1和D2:分别是“C”时钟上升沿和下降沿同步输入的SDR数据。 CE:时钟使能信号; S/R:置位/复位信号,这两个信号不能同时拉高。 ODDR原语模板如下: - ODDR #(
- .DDR_CLK_EDGE("OPPOSITE_EDGE"),// "OPPOSITE_EDGE" or "SAME_EDGE"
- .INIT(1'b0),// Initial value ofQ: 1'b0 or 1'b1
- .SRTYPE("SYNC")// Set/Reset type: "SYNC" or "ASYNC"
- ) ODDR_inst (
- .Q(Q),// 1-bit DDR output
- .C(C),// 1-bit clock input
- .CE(CE),// 1-bit clock enableinput
- .D1(D1),// 1-bit data input(positive edge)
- .D2(D2),// 1-bit data input(negative edge)
- .R(R),// 1-bit reset
- .S(S)// 1-bit set
- );
复制代码DDR_CLK_EDGE参数为ODDR的两种输出模式,分别为“OPPOSITE_EDGE”和“SAME_EDGE”模式。 OPPOSITE_EDGE模式的时序图如下图所示: 图25.1.20 ODDR OPPOSITE_EDGE模式时序图 此种模式下,在FPGA内部需要两个反相时钟来同步D1和D2,此种模式使用较少。 SAME_EDGE模式的时序图如下图所示: 图25.1.21 ODDR SAME_EDGE模式时序图 此种模式下,数据可以在相同的时钟边沿输出到Q,一般采用此种模式。 IDELAYE2:IO延时原语,用于在信号通过引脚进入芯片内部之前,进行延时调节,一般高速端口信号由于走线延时等原因,需要通过IDELAYE2原语对数据做微调。IDELAYE2原语模板如下: - IDELAYE2 #(
- .CINVCTRL_SEL("FALSE"),// Enable dynamic clock inversion (FALSE, TRUE)
- .DELAY_SRC("IDATAIN"),// Delay input (IDATAIN, DATAIN)
- .HIGH_PERFORMANCE_MODE("FALSE"),// Reduced jitter ("TRUE"), Reduced power("FALSE")
- .IDELAY_TYPE("FIXED"),// FIXED, VARIABLE, VAR_LOAD, VAR_LOAD_PIPE
- .IDELAY_VALUE(0),// Input delay tap setting (0-31)
- .PIPE_SEL("FALSE"),// Select pipelined mode, FALSE, TRUE
- .REFCLK_FREQUENCY(200.0),// IDELAYCTRL clock input frequency in MHz
- .SIGNAL_PATTERN("DATA")// DATA, CLOCK input signal
- )
- IDELAYE2_inst (
- .CNTVALUEOUT(CNTVALUEOUT),// 5-bit output:Counter value output
- .DATAOUT(DATAOUT),// 1-bit output:Delayed data output
- .C(C),// 1-bit input: Clock input
- .CE(CE),// 1-bit input:Active high enable increment/decrement input
- .CINVCTRL(CINVCTRL),// 1-bit input:Dynamic clock inversion input
- .CNTVALUEIN(CNTVALUEIN),// 5-bit input:Counter value input
- .DATAIN(DATAIN),// 1-bit input:Internal delay data input
- .IDATAIN(IDATAIN),// 1-bit input: Datainput from the I/O
- .INC(INC),// 1-bit input:Increment / Decrement tap delay input
- .LD(LD),// 1-bit input: LoadIDELAY_VALUE input
- .LDPIPEEN(LDPIPEEN),// 1-bit input:Enable PIPELINE register to load data input
- .REGRST(REGRST)// 1-bit input:Active-high reset tap-delay input
- );
复制代码 ATAIN为延时前的输入信号,DATAOUT为延时后的输出信号。 REFCLK_FREQUENCY参数为IDELAYCTRL原语的参考时钟频率,一般为200Mhz;IDELAY_VALUE参数用来设置延时的tap数,范围为1~31,每个tap数的延时时间和参考时钟频率有关。 和IDELAYE2对应的还有ODELAYE2,由于A7系列没有ODELAYE2原语,故此处不做讨论。 IDELAYCTRL:IDELAYCTRL和IDELAYE2一般同时使用,IDELAYCTRL对IDELAYE2延时进行校准。IDELAYE2原语如下: - (* IODELAY_GROUP =<iodelay_group_name>*)
- IDELAYCTRL IDELAYCTRL_inst (
- .RDY(RDY),// 1-bit output:Ready output
- .REFCLK(REFCLK),// 1-bit input:Reference clock input
- .RST(RST)// 1-bit input: Activehigh reset input
- );
复制代码IODELAY_GROUP为延时IO分组,一般数据接口位于多个BANK时,才需要分组。 IDELAYCTRL通过参考时钟REFCLK来校准IDELAY2每个tap的延时值,可用的REFCLK频率为190Mhz~210Mhz或者290Mhz~310Mhz。时钟频率越高对应的tap延时平均值越小,即延时调节精度越高。当参考时钟为200Mhz时,一个tap为78ps。 1.2 实验任务本节实验任务是使用领航者ZYNQ开发板上的PL端以太网接口,和上位机实现ARP请求和应答的功能。当上位机发送ARP请求时,开发板返回ARP应答数据。当按下开发板的触摸按键时,开发板发送ARP请求,此时上位机返回应答数据。 1.3 硬件设计PL端千兆以太网接口部分的硬件设计原理和“MDIO接口读写测试实验”完全相同,请参考“MDIO接口读写测试实验”中的硬件设计部分。 本实验中,各端口信号的管脚分配如下表所示: 表25.3.1以太网ARP测试实验管脚分配 对应的XDC约束语句如下所示: - create_clock -period 20.000 -name sys_clk[get_ports sys_clk]
- create_clock -period 8.000 -name eth_rxc[get_ports eth_rxc]
- set_property -dict {PACKAGE_PIN U18 IOSTANDARDLVCMOS33} [get_ports sys_clk]
- set_property -dict {PACKAGE_PIN J15 IOSTANDARDLVCMOS33} [get_ports sys_rst_n]
- set_property -dict {PACKAGE_PIN L19 IOSTANDARDLVCMOS33} [get_ports touch_key]
- #GE_PL
- set_property -dict {PACKAGE_PIN G14 IOSTANDARDLVCMOS33} [get_ports eth_rst_n]
- set_property -dict {PACKAGE_PIN K17 IOSTANDARDLVCMOS33} [get_ports eth_rxc]
- set_property -dict {PACKAGE_PIN D19 IOSTANDARDLVCMOS33} [get_ports eth_rx_ctl]
- set_property -dict {PACKAGE_PIN F19 IOSTANDARDLVCMOS33} [get_ports {eth_rxd[0]}]
- set_property -dict {PACKAGE_PIN F20 IOSTANDARDLVCMOS33} [get_ports {eth_rxd[1]}]
- set_property -dict {PACKAGE_PIN E17 IOSTANDARDLVCMOS33} [get_ports {eth_rxd[2]}]
- set_property -dict {PACKAGE_PIN D18 IOSTANDARDLVCMOS33} [get_ports {eth_rxd[3]}]
- set_property -dict {PACKAGE_PIN G19 IOSTANDARDLVCMOS33} [get_ports eth_txc]
- set_property -dict {PACKAGE_PIN E19 IOSTANDARDLVCMOS33} [get_ports eth_tx_ctl]
- set_property -dict {PACKAGE_PIN G20 IOSTANDARDLVCMOS33} [get_ports {eth_txd[0]}]
- set_property -dict {PACKAGE_PIN F16 IOSTANDARDLVCMOS33} [get_ports {eth_txd[1]}]
- set_property -dict {PACKAGE_PIN F17 IOSTANDARDLVCMOS33} [get_ports {eth_txd[2]}]
- set_property -dict {PACKAGE_PIN E18 IOSTANDARDLVCMOS33} [get_ports {eth_txd[3]}]
复制代码 1.4 程序设计 根据实验任务,我们可以大致规划出系统的控制流程:首先我们需要完成RGMII接口数据和GMII接口数据的转换,以方便数据的采集和解析,在数据采集过程中所用到的延时原语参考时钟由锁相环输出的时钟提供;其次整个以太网帧格式与ARP协议的实现由ARP顶层模块完成;ARP控制模块负责检测输入的触摸按键是否被按下,控制ARP顶层模块发起请求与产生应答等操作。由此画出系统的功能框图如下图所示: 系统时钟经过PLL时钟模块后,输出200Mhz的时钟,用于IDELAYCTRL原语的参考时钟;GMII TO RGMII模块负责将双沿(DDR)数据和单沿(SDR)数据之间的转换;ARP顶层模块实现了以太网ARP数据包的接收、发送以及CRC校验的功能;ARP控制模块根据输入的按键触摸信号和接收到的ARP请求信号,控制ARP顶层模块发送ARP请求或者ARP应答。 各模块端口及信号连接如下图所示: 由上图可知,FPGA顶层模块例化了以下四个模块,PLL时钟模块(clk_wiz)、GMII TO RGMII模块(gmii_to_rgmii)、ARP顶层模块(arp)和ARP控制模块(arp_ctrl),实现了各模块之间的数据交互。 其中ARP顶层模块和GMII TORGMII模块内部也例化了多个其它模块,这样设计的目的是为了方便模块的重用。 顶层模块的代码如下: - 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};
- //输入数据IO延时(如果为n,表示延时n*78ps)
- parameterIDELAY_VALUE =16;
- //wire define
- wire clk_200m ;//用于IO延时的时钟
- 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 arp_tx_en ;//ARP发送使能信号
- wire arp_tx_type;//ARP发送类型 0:请求 1:应答
- wire tx_done ;//发送的目标MAC地址
- wire[47:0 des_mac ;//发送的目标IP地址
- wire[31:0 des_ip ;//以太网发送完成信号
- //*****************************************************
- //** main code
- //*****************************************************
- assign des_mac = src_mac;
- assign des_ip = src_ip;
- assign eth_rst_n = sys_rst_n;
- //MMCM/PLL
- clk_wiz u_clk_wiz
- (
- .clk_in1 (sys_clk ),
- .clk_out1 (clk_200m ),
- .reset (~sys_rst_n),
- .locked (locked)
- );
- //GMII接口转RGMII接口
- gmii_to_rgmii
- #(
- .IDELAY_VALUE (IDELAY_VALUE)
- )
- u_gmii_to_rgmii(
- .idelay_clk (clk_200m ),
- .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 ),
- .rgmii_rxc (eth_rxc ),
- .rgmii_rx_ctl (eth_rx_ctl ),
- .rgmii_rxd (eth_rxd ),
- .rgmii_txc (eth_txc ),
- .rgmii_tx_ctl (eth_tx_ctl ),
- .rgmii_txd (eth_txd )
- );
- //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控制
- rp_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地址。 程序的第25行设置IDELAY_VALUE的值为8,由于IDELAYCTRL原语的参考时钟为200Mhz,对应每个tap的延时时间为78ps,RGMII输入数据和控制信号相对于RXC时钟共延时了16*78ps=1248ps,这也是我们实测非常稳定的一个值。 程序的第52行和53行代码将收到的对端设备MAC地址和目的IP地址,作为开发板发送时的目的MAC地址和IP地址。 gmii_to_rgmii模块代码如下: - module gmii_to_rgmii(
- input idelay_clk ,//IDELAY时钟
- //以太网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发送数据
- );
- //parameter define
- parameterIDELAY_VALUE =0;//输入数据IO延时(如果为n,表示延时n*78ps)
- //*****************************************************
- //** main code
- //*****************************************************
- assign gmii_tx_clk = gmii_rx_clk;
- //RGMII接收
- rgmii_rx
- #(
- .IDELAY_VALUE (IDELAY_VALUE)
- )
- u_rgmii_rx(
- .idelay_clk (idelay_clk),
- .gmii_rx_clk (gmii_rx_clk),
- .rgmii_rxc (rgmii_rxc ),
- .rgmii_rx_ctl (rgmii_rx_ctl),
- .rgmii_rxd (rgmii_rxd ),
- .gmii_rx_dv (gmii_rx_dv),
- .gmii_rxd (gmii_rxd )
- );
- //RGMII发送
- rgmii_tx u_rgmii_tx(
- .gmii_tx_clk (gmii_tx_clk),
- .gmii_tx_en (gmii_tx_en ),
- .gmii_txd (gmii_txd ),
- .rgmii_txc (rgmii_txc ),
- .rgmii_tx_ctl (rgmii_tx_ctl),
- .rgmii_txd (rgmii_txd )
- );
复制代码 由该模块的端口可知,该模块实现了双沿(DDR)数据和单沿(SDR)数据之间的转换。程序中第26行将GMII接收时钟赋值给GMII发送时钟,因此GMII的发送时钟和接收时钟实际上为同一个时钟。GMII TO RGMII模块例化了rgmii_rx模块和rgmii_tx模块。 rgmii_rx模块代码如下所示: - module rgmii_rx(
- input idelay_clk ,//200Mhz时钟,IDELAY时钟
- //以太网RGMII接口
- input rgmii_rxc ,//RGMII接收时钟
- input rgmii_rx_ctl,//RGMII接收数据控制信号
- input[3:0 rgmii_rxd ,//RGMII接收数据
- //以太网GMII接口
- output gmii_rx_clk ,//GMII接收时钟
- output gmii_rx_dv ,//GMII接收数据有效信号
- output[7:0 gmii_rxd //GMII接收数据
- );
- //parameter define
- parameterIDELAY_VALUE =0;
- //wire define
- wire rgmii_rxc_bufg;//全局时钟缓存
- wire rgmii_rxc_bufio;//全局时钟IO缓存
- wire[3:0 rgmii_rxd_delay;//rgmii_rxd输入延时
- wire rgmii_rx_ctl_delay;//rgmii_rx_ctl输入延时
- wire[1:0 gmii_rxdv_t;//两位GMII接收有效信号
- //*****************************************************
- //** main code
- //*****************************************************
- assign gmii_rx_clk = rgmii_rxc_bufg;
- assign gmii_rx_dv = gmii_rxdv_t[0]&gmii_rxdv_t[1];
- //全局时钟缓存
- BUFG BUFG_inst (
- .I (rgmii_rxc),// 1-bit input: Clockinput
- .O (rgmii_rxc_bufg)// 1-bit output: Clockoutput
- );
- //全局时钟IO缓存
- BUFIO BUFIO_inst (
- .I (rgmii_rxc),// 1-bit input: Clockinput
- .O (rgmii_rxc_bufio)// 1-bit output: Clockoutput
- );
- //输入延时控制
- // Specifies group name for associated IDELAYs/ODELAYs andIDELAYCTRL
- (* IODELAY_GROUP ="rgmii_rx_delay"*)
- IDELAYCTRL IDELAYCTRL_inst (
- .RDY(),// 1-bit output:Ready output
- .REFCLK(idelay_clk),// 1-bit input: Reference clock input
- .RST(1'b0)// 1-bit input: Activehigh reset input
- );
- //rgmii_rx_ctl输入延时与双沿采样
- (* IODELAY_GROUP ="rgmii_rx_delay"*)
- IDELAYE2 #(
- .IDELAY_TYPE ("FIXED"),// FIXED, VARIABLE, VAR_LOAD, VAR_LOAD_PIPE
- .IDELAY_VALUE (IDELAY_VALUE),// Input delay tapsetting (0-31)
- .REFCLK_FREQUENCY(200.0)// IDELAYCTRL clockinput frequency in MHz
- )
- u_delay_rx_ctrl (
- .CNTVALUEOUT (),// 5-bit output: Counter value output
- .DATAOUT (rgmii_rx_ctl_delay),// 1-bit output:Delayed data output
- .C (1'b0),// 1-bit input: Clock input
- .CE (1'b0),// 1-bit input: enable increment/decrement
- .CINVCTRL (1'b0),// 1-bit input: Dynamic clock inversion input
- .CNTVALUEIN (5'b0),// 5-bit input: Counter value input
- .DATAIN (1'b0),// 1-bit input: Internal delay data input
- .IDATAIN (rgmii_rx_ctl),// 1-bit input: Datainput from the I/O
- .INC (1'b0),// 1-bit input: Increment / Decrement tap delay
- .LD (1'b0),// 1-bit input: Load IDELAY_VALUE input
- .LDPIPEEN (1'b0),// 1-bit input: Enable PIPELINE register
- .REGRST (1'b0)// 1-bit input: Active-high reset tap-delay input
- );
- //输入双沿采样寄存器
- IDDR #(
- .DDR_CLK_EDGE("SAME_EDGE_PIPELINED"),//"OPPOSITE_EDGE", "SAME_EDGE"
- // or"SAME_EDGE_PIPELINED"
- .INIT_Q1 (1'b0),// Initial value of Q1: 1'b0 or 1'b1
- .INIT_Q2 (1'b0),// Initial value of Q2: 1'b0 or 1'b1
- .SRTYPE ("SYNC")// Set/Reset type: "SYNC" or "ASYNC"
- ) u_iddr_rx_ctl (
- .Q1 (gmii_rxdv_t[0]),// 1-bit output forpositive edge of clock
- .Q2 (gmii_rxdv_t[1]),// 1-bit output fornegative edge of clock
- .C (rgmii_rxc_bufio),// 1-bit clock input
- .CE (1'b1),// 1-bit clock enable input
- .D (rgmii_rx_ctl_delay),// 1-bit DDR datainput
- .R (1'b0),// 1-bit reset
- .S (1'b0)// 1-bit set
- );
- //rgmii_rxd输入延时与双沿采样
- genvar i;
- generatefor(i=0; i<4; i=i+1)
- (* IODELAY_GROUP ="rgmii_rx_delay"*)
- begin: rxdata_bus
- //输入延时
- (* IODELAY_GROUP ="rgmii_rx_delay"*)
- IDELAYE2 #(
- .IDELAY_TYPE ("FIXED"),// FIXED,VARIABLE,VAR_LOAD,VAR_LOAD_PIPE
- .IDELAY_VALUE (IDELAY_VALUE),// Input delay tapsetting (0-31)
- .REFCLK_FREQUENCY(200.0)// IDELAYCTRL clockinput frequency in MHz
- )
- u_delay_rxd (
- .CNTVALUEOUT (),// 5-bit output: Counter value output
- .DATAOUT (rgmii_rxd_delay[i]),// 1-bit output: Delayed data output
- .C (1'b0),// 1-bit input: Clock input
- .CE (1'b0),// 1-bit input: enable increment/decrement
- .CINVCTRL (1'b0),// 1-bit input: Dynamic clock inversion
- .CNTVALUEIN (5'b0),// 5-bit input: Counter value input
- .DATAIN (1'b0),// 1-bit input: Internal delay data input
- .IDATAIN (rgmii_rxd[i]),// 1-bit input: Data input from the I/O
- .INC (1'b0),// 1-bit input: Inc/Decrement tap delay
- .LD (1'b0),// 1-bit input: Load IDELAY_VALUE input
- .LDPIPEEN (1'b0),// 1-bit input: Enable PIPELINE register
- .REGRST (1'b0)// 1-bit input: Active-high reset tap-delay
- );
- //输入双沿采样寄存器
- IDDR #(
- .DDR_CLK_EDGE("SAME_EDGE_PIPELINED"),//"OPPOSITE_EDGE", "SAME_EDGE"
- // or"SAME_EDGE_PIPELINED"
- .INIT_Q1 (1'b0),// Initial value of Q1: 1'b0 or 1'b1
- .INIT_Q2 (1'b0),// Initial value of Q2: 1'b0 or 1'b1
- .SRTYPE ("SYNC")// Set/Reset type: "SYNC" or "ASYNC"
- ) u_iddr_rxd (
- .Q1 (gmii_rxd[i]),// 1-bit output for positive edge of clock
- .Q2 (gmii_rxd[4+i]),// 1-bit output for negative edge of clock
- .C (rgmii_rxc_bufio),// 1-bit clock inputrgmii_rxc_bufio
- .CE (1'b1),// 1-bit clock enable input
- .D (rgmii_rxd_delay[i]),// 1-bit DDR data input
- .R (1'b0),// 1-bit reset
- .S (1'b0)// 1-bit set
- );
- end
- endgenerate
- endmodule
复制代码 该模块通过调用BUFG、BUFIO、IDDR、IDELAYCTRL和IDELAY2原语,实现了RGMII接口输入的DDR数据到SDR数据的转换,输入的rgmii_rx_ctl控制信号的转换方法同样类似。rgmii_rx模块信号转换示意图如下图所示: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image046.gif 时钟专用引脚输入的rgmii_rxc时钟经过BUFG后,得到rgmii_rxc_bufg(gmii_rx_clk),该时钟为全局缓冲时钟,其到达FPGA内部的IOB、CLB、块RAM的时钟延迟和抖动非常小,为其他模块提供操作时钟;另外rgmii_rxc时钟也经过BUFIO,专门用于采集IO端口的数据。 rgmii_rx_ctl控制信号和4位rgmii_rxd数据先经过IDELAY2进行延时,再经过IDDR将双沿1位数据转换成单沿两位数据。其中IDELAY2原语必须搭配IDELAYCTRL原语进行使用。 另外,在程序的第93行至136行代码通过generatefor语句实现对IDELAY2和IDDR的例化,由于输入的数据引脚为4位数据,因此这里共例化了4次。其等效于分别对输入的IDELAY2和IDDR例化4次,这里采用generatefor的写法可以减少很多的代码量,当需要对某个模块例化较多次数时,这种写法能够大大提高效率。 rgmii_tx模块代码如下所示: - module rgmii_tx(
- //GMII发送端口
- input gmii_tx_clk ,//GMII发送时钟
- input gmii_tx_en ,//GMII输出数据有效信号
- input[7:0 gmii_txd ,//GMII输出数据
- //RGMII发送端口
- output rgmii_txc ,//RGMII发送数据时钟
- output rgmii_tx_ctl,//RGMII输出数据有效信号
- output[3:0 rgmii_txd //RGMII输出数据
- );
- //*****************************************************
- //** main code
- //*****************************************************
- assign rgmii_txc = gmii_tx_clk;
- //输出双沿采样寄存器 (rgmii_tx_ctl)
- ODDR #(
- .DDR_CLK_EDGE ("SAME_EDGE"),// "OPPOSITE_EDGE" or "SAME_EDGE"
- .INIT (1'b0),// Initial value of Q: 1'b0 or 1'b1
- .SRTYPE ("SYNC")// Set/Reset type: "SYNC" or "ASYNC"
- ) ODDR_inst (
- .Q (rgmii_tx_ctl),// 1-bit DDR output
- .C (gmii_tx_clk),// 1-bit clock input
- .CE (1'b1),// 1-bit clock enable input
- .D1 (gmii_tx_en),// 1-bit data input(positive edge)
- .D2 (gmii_tx_en),// 1-bit data input(negative edge)
- .R (1'b0),// 1-bit reset
- .S (1'b0)// 1-bit set
- );
- genvar i;
- generatefor(i=0; i<4; i=i+1)
- begin: txdata_bus
- //输出双沿采样寄存器 (rgmii_txd)
- ODDR #(
- .DDR_CLK_EDGE ("SAME_EDGE"),// "OPPOSITE_EDGE" or "SAME_EDGE"
- .INIT (1'b0),// Initial value of Q: 1'b0 or 1'b1
- .SRTYPE ("SYNC")// Set/Reset type: "SYNC" or "ASYNC"
- ) ODDR_inst (
- .Q (rgmii_txd[i]),// 1-bit DDR output
- .C (gmii_tx_clk),// 1-bit clock input
- .CE (1'b1),// 1-bit clock enable input
- .D1 (gmii_txd[i]),// 1-bit data input (positive edge)
- .D2 (gmii_txd[4+i]),// 1-bit data input (negative edge)
- .R (1'b0),// 1-bit reset
- .S (1'b0)// 1-bit set
- );
- end
- endgenerate
- endmodule
复制代码 该模块通过调用ODDR原语将输入的单沿8位数据(gmii_txd)转换成双沿采样的4位数据(rgmii_txd),gmii_tx_en和rgmii_tx_ctl信号的处理方法同样类似。rgmii_tx模块信号转换示意图如下图所示: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image048.gif gmii_tx_en数据使能信号和8位gmii_txd数据经过ODDR将单沿2位数据转换成双沿1位数据。需要说明的是,在程序的第34行至第52行同样通过generatefor语句实现原语的多次例化。 ARP顶层模块实现了整个以太网帧格式与ARP协议的功能,其模块端口及信号连接如下图所示: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image050.jpg 图25.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接收模块通过状态机来解析数据,其状态跳转图如下图所示: file:///C:/Users/ADMINI~1/AppData/Local/Temp/msohtmlclip1/01/clip_image052.gif 接收模块使用三段式状态机来解析以太网包,从上图可以比较直观的看到每个状态实现的功能以及跳转到下一个状态的条件。这里需要注意的一点是,在中间状态如前导码错误、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 :begin
- if(gmii_rx_dv)begin
- cnt <= cnt +5'd1;
- if(cnt ==5'd6)
- op_data[15:8]<= gmii_rxd;//操作码
- elseif(cnt ==5'd7)
- op_data[7:0]<= gmii_rxd;
- elseif(cnt >=5'd8&&cnt <5'd14)//源MAC地址
- src_mac_t <={src_mac_t[39:0],gmii_rxd};
- elseif(cnt >=5'd14&&cnt <5'd18)//源IP地址
- src_ip_t<={src_ip_t[23:0],gmii_rxd};
- elseif(cnt >=5'd24&&cnt <5'd28)//目标IP地址
- des_ip_t <={des_ip_t[23:0],gmii_rxd};
- elseif(cnt ==5'd28)begin
- cnt <=5'd0;
- if(des_ip_t == BOARD_IP)begin//判断目的IP地址和操作码
- if((op_data ==16'd1)||(op_data==16'd2))begin
- skip_en <=1'b1;
- arp_rx_done <=1'b1;
- src_mac <= src_mac_t;
- src_ip <= src_ip_t;
- src_mac_t <=48'd0;
- src_ip_t <=32'd0;
- des_mac_t <=48'd0;
- des_ip_t <=32'd0;
- if(op_data ==16'd1)
- arp_rx_type <=1'b0;//ARP请求
- else
- arp_rx_type <=1'b1;//ARP应答
- end
- else
- error_en <=1'b1;
- end
- else
- error_en <=1'b1;
- end
- end
- end
- st_rx_end :begin
- cnt <=5'd0;
- //单包数据接收完成
- if(gmii_rx_dv ==1'b0&&skip_en ==1'b0)
- skip_en <=1'b1;
- end
复制代码 st_arp_data状态根据ARP协议解析数据,在程序的第200行至第201行代码判断目的IP地址和OP操作码是否正确,如果错误,则丢弃该包数据。在程序的第210行至第213行代码根据操作码为arp_rx_type(接收到的ARP数据包类型)赋值,当接收到ARP请求包时,arp_rx_type等于0;当接收到ARP应答包时,arp_rx_type等于1。 ARP接收过程中采集的ILA波形图如图25.4.7所示,gmii_rx_dv信号拉高表示此时输入的数据有效,根据gmii_rxd的值来解析数据。从图中可以看出,发送端的MAC地址和地址,以及当前接收到的以太网数据包类型为ARP(0x0806)。在接收完ARP数据包之后,拉高arp_rx_done信号表示接收完成,图中arp_rx_type信号为低电平,表示当前接收到的是ARP请求数据包。 ARP发送模块则是根据以太网帧格式是ARP协议发送数据,也就是接收模块的逆过程,同样也非常适合使用状态机来完成发送数据的功能,状态跳转图如下图所示: 发送模块和接收模块有很多相似之处,同样使用三段式状态机来发送以太网包,从上图可以比较直观的看到每个状态实现的功能以及跳转到下一个状态的条件。 发送模块的代码中定义了数组来存储前导码+帧头、以太网的帧头、ARP数据,在复位时初始化数组的值,部分源代码如下。 - reg[7:0 preamble[7:0];//前导码+SFD
- reg[7:0 eth_head[13:0];//以太网首部
- reg[7:0 arp_data[27:0];//ARP数据
- 省略部分代码……
- //初始化数组
- //前导码 7个8'h55 + 1个8'hd5
- preamble[0]<=8'h55;
- preamble[1]<=8'h55;
- preamble[2]<=8'h55;
- preamble[3]<=8'h55;
- preamble[4]<=8'h55;
- preamble[5]<=8'h55;
- preamble[6]<=8'h55;
- preamble[7]<=8'hd5;
- //以太网帧头
- eth_head[0]<=DES_MAC[47:40];//目的MAC地址
- eth_head[1]<=DES_MAC[39:32];
- eth_head[2]<=DES_MAC[31:24];
- eth_head[3]<=DES_MAC[23:16];
- eth_head[4]<=DES_MAC[15:8];
- eth_head[5]<=DES_MAC[7:0];
- eth_head[6]<=BOARD_MAC[47:40];//源MAC地址
- eth_head[7]<=BOARD_MAC[39:32];
- eth_head[8]<=BOARD_MAC[31:24];
- eth_head[9]<=BOARD_MAC[23:16];
- eth_head[10]<=BOARD_MAC[15:8];
- eth_head[11]<=BOARD_MAC[7:0];
- eth_head[12]<=ETH_TYPE[15:8];//以太网帧类型
- eth_head[13]<=ETH_TYPE[7:0];
- //ARP数据
- arp_data[0]<=HD_TYPE[15:8];//硬件类型
- arp_data[1]<=HD_TYPE[7:0];
- arp_data[2]<=PROTOCOL_TYPE[15:8];//上层协议类型
- arp_data[3]<=PROTOCOL_TYPE[7:0];
- arp_data[4]<=8'h06;//硬件地址长度,6
- arp_data[5]<=8'h04;//协议地址长度,4
- arp_data[6]<=8'h00;//OP,操作码 8'h01:ARP请求8'h02:ARP应答
- arp_data[7]<=8'h01;
- arp_data[8]<=BOARD_MAC[47:40];//发送端(源)MAC地址
- arp_data[9]<=BOARD_MAC[39:32];
- arp_data[10]<=BOARD_MAC[31:24];
- arp_data[11]<=BOARD_MAC[23:16];
- arp_data[12]<=BOARD_MAC[15:8];
- arp_data[13]<=BOARD_MAC[7:0];
- arp_data[14]<=BOARD_IP[31:24];//发送端(源)IP地址
- arp_data[15]<=BOARD_IP[23:16];
- arp_data[16]<=BOARD_IP[15:8];
- arp_data[17]<=BOARD_IP[7:0];
- arp_data[18]<=DES_MAC[47:40];//接收端(目的)MAC地址
- arp_data[19]<=DES_MAC[39:32];
- arp_data[20]<=DES_MAC[31:24];
- arp_data[21]<=DES_MAC[23:16];
- arp_data[22]<=DES_MAC[15:8];
- arp_data[23]<=DES_MAC[7:0];
- arp_data[24]<=DES_IP[31:24];//接收端(目的)IP地址
- arp_data[25]<=DES_IP[23:16];
- arp_data[26]<=DES_IP[15:8];
- arp_data[27]<=DES_IP[7:0];
复制代码在程序的第220行至241行代码,根据输入的发送类型、目的MAC地址和IP地址,重新更新数组里的值。 - st_arp_data :begin//发送ARP数据
- crc_en <=1'b1;
- gmii_tx_en <=1'b1;
- //至少发送46个字节
- if(cnt == MIN_DATA_NUM -1'b1)begin
- skip_en <=1'b1;
- cnt <=1'b0;
- data_cnt <=1'b0;
- end
- else
- cnt <= cnt +1'b1;
- if(data_cnt <=6'd27)begin
- data_cnt <= data_cnt +1'b1;
- gmii_txd <= arp_data[data_cnt];
- end
- else
- gmii_txd <=8'd0;//Padding,填充0
- end
- 程序第265行至第282行代码为发送ARP数据的状态。我们前面讲过以太网帧格式的数据部分最少是46个字节,ARP数据只有28个字节,因此在发送完ARP数据之后补充发送18个字节,填充的数据为0。
- st_crc :begin//发送CRC校验值
- gmii_tx_en <=1'b1;
- cnt <= cnt +1'b1;
- if(cnt ==6'd0)
- gmii_txd <={~crc_next[0],~crc_next[1],~crc_next[2],~crc_next[3],
- ~crc_next[4],~crc_next[5],~crc_next[6],~crc_next[7]};
- elseif(cnt ==6'd1)
- gmii_txd <={~crc_data[16],~crc_data[17],~crc_data[18],
- ~crc_data[19],~crc_data[20],~crc_data[21],
- ~crc_data[22],~crc_data[23]};
- elseif(cnt ==6'd2)begin
- gmii_txd <={~crc_data[8],~crc_data[9],~crc_data[10],
- ~crc_data[11],~crc_data[12],~crc_data[13],
- ~crc_data[14],~crc_data[15]};
- end
- elseif(cnt ==6'd3)begin
- gmii_txd <={~crc_data[0],~crc_data[1],~crc_data[2],~crc_data[3],
- ~crc_data[4],~crc_data[5],~crc_data[6],~crc_data[7]};
- tx_done_t <=1'b1;
- skip_en <=1'b1;
- cnt <=1'b0;
- end
- end
复制代码 程序的第283行至305行代码为发送CRC校验值状态,发送模块的CRC校验是由crc32_d4模块完成的,发送模块将输入的crc的计算结果每4位高低位互换,按位取反发送出去,crc计算部分在后面阐述。 ARP发送过程中采集的ILA波形图如图25.4.9所示,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的原理与公式推导较复杂,在此可不比深究,CRC校验的源代码可直接通过网页生成工具直接下载,网址: http://www.easics.com/webtools/crctool,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控制模块的代码如下:
ARP控制模块的代码较简单,首先检测输入触摸按键的上升沿,当检测到上升沿之后,触发ARP顶层模块发起ARP请求信号;同时检测输入的arp_rx_done和arp_rx_type信号,当接收上位机的ARP请求信号后,触发ARP顶层模块发送ARP应答信号,将开发板的MAC地址发送给上位机。 1.5 下载验证将下载器一端连接电脑,另一端与开发板上的JTAG下载口连接,将网线一端连接开发板的PL网口(GE_PL),另一端连接电脑的网口,接下来连接电源线,并打开开发板的电源开关。GE_PL网口的位置如下图所示。 点击Vivado左侧“FlowNavigator”窗口最下面的“Open Hardware Manager”,此时Vivado软件识别到下载器,点击“Hardware”窗口中“Program Device”下载程序,在弹出的界面中选择“Program”下载程序。 程序下载完成后,PHY芯片会和电脑网卡进行通信(自协商),如果程序下载正确并且硬件连接无误的话,我们点击电脑右下角的网络图标,会看到本地连接刚开始显示的是正在识别,一段时间之后显示未识别的网络,打开方式如下图所示(WIN7和WIN10操作可能存在差异,但基本相同)。 - module arp_ctrl(
- input clk ,//输入时钟
- input rst_n ,//复位信号,低电平有效
- input touch_key ,//触摸按键,用于触发开发板发出ARP请求
- input arp_rx_done,//ARP接收完成信号
- input arp_rx_type,//ARP接收类型 0:请求 1:应答
- outputreg arp_tx_en ,//ARP发送使能信号
- outputreg 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 ornegedge rst_n)begin
- if(!rst_n)begin
- touch_key_d0 <=1'b0;
- touch_key_d1 <=1'b0;
- end
- elsebegin
- touch_key_d0 <= touch_key;
- touch_key_d1 <= touch_key_d0;
- end
- end
- //为arp_tx_en和arp_tx_type赋值
- always@(posedge clk ornegedge rst_n)begin
- if(!rst_n)begin
- arp_tx_en <=1'b0;
- arp_tx_type <=1'b0;
- end
- elsebegin
- if(pos_touch_key ==1'b1)begin//检测到输入触摸按键上升沿
- arp_tx_en <=1'b1;
- arp_tx_type <=1'b0;
- end
- //接收到ARP请求,开始控制ARP发送模块应答
- elseif((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
复制代码 点击图24.5.3中的“未识别的网络(无Internet)”,弹出如下图所示界面。 点击“更改适配器”选项,弹出如下图所示界面。 如果看到上图“以太网”显示未识别的网络之后,说明硬件连接和程序都是没有问题的,接下来设置以太网的IP地址,改成代码中设置的目的IP地址,顶层模块参数定义如下: //目的IP地址192.168.1.102 parameter DES_IP ={8'd192,8'd168,8'd1,8'd102}; 因此接下来将电脑以太网的IP地址设置成192.168.1.102。鼠标右击图24.5.5中的以太网,如下图所示: 点击“属性”,弹出如下图所示界面。 鼠标双击“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”,如图25.5.11和图25.5.12所示。 由上图可知,此时以太网接口的ARP缓存表中已经添加了开发板的IP地址(192.168.1.10)和MAC地址(00-11-22-33-44-55),说明开发板发送ARP请求成功。如果大家操作失败,请检查开发板的PL网口(GE_PL)是否通过网线连接电脑的网口,并且此时开发板已经下载程序以及通过按下触摸按键(TPAD)来触发ARP请求。另外,如果电脑网口不支持千兆网通信,那么也会导致ARP操作失败。 接下来我们再来通过电脑发起ARP请求,验证开发板有没有正确返回ARP应答。我们先从以太网ARP缓存表中删除开发板的MAC地址,删除方法是在DOS命令中输入“arp -d”,并按下按键的回车键,如下图所示: 接下来重新在DOS命令中输入“arp -a”,来验证是否删除成功(如果没有以管理员运行会导致删除失败),输入完成后,如下图所示; 此时我们之前获取的开发板MAC地址已经删除成功,接下来在DOS命令中输入“ping192.168.1.10”,来让电脑发起ARP请求,如下图所示。 需要说明的是,ping是一个十分强大的TCP/IP工具,它可以用来检测网络的连通情况和分析网络速度。ping命令是一个固定格式的ICMP(Internet控制报文协议)请求数据包,之后会发起ARP请求命令,所以我们这里是通过ping命令来间接发起ARP请求。由于开发板并没有实现ICMP协议,因此在ping时会请求超时,但是在ping的过程中发起的ARP请求,开发板会响应并返回ARP应答数据。 接下来再次在DOS命令中输入“arp-a”,查询是否成功获取到开发板MAC地址,如下图所示: 由上图可知,电脑正确获取到开发板的MAC地址,并更新至ARP缓存表中。到这里,开发板实现的ARP协议就已经全部验证成功了。 接下来介绍一个以太网通信时经常使用的抓包软件,该软件位于开发板所随附的资料“6_软件资料/1_软件/Wireshark”目录下,也可以直接在网上搜索下载,我们现在打开Wireshark,界面如下图所示: 双击上图所示的以太网或者先选中以太网,再点击上方红框选中的蓝色按钮,即可开始抓取本地连接的数据包,抓取界面如下图所示: 从上图可以看到,已经抓取到其它应用程序使用以太网发送的数据包,但是这些数据包并不是开发板发送的数据包,我们这个时候按下开发板的触摸按键(TPAD),就可以在wireshark中抓取到数据包了,抓取到的数据包如下图所示: 上图中第38行数据包是开发板发送给电脑的ARP请求包,第39行数据包是电脑发送给开发板的ARP应答包,此时双击第38行即可看到开发板发送的详细数据,如下图所示: 图25.5.20wireshark抓取到的详细数据 上图中下方红框为开发板发送的16进制数据(去掉前导码、SFD和CRC值),可以看到,后面的18个0就是我们在发送时填充的18个字节数据。需要说明的是,当打开第39行电脑返回的ARP请求包时,看不到填充的0,这是由于后面填充的数据是网卡自动填充的,因此wireshark中会看不到。
|