搜索
bottom↓
回复: 0

【正点原子FPGA连载】第二十五章以太网ARP测试实验--摘自【正点原子】领航者 ZYNQ 之FPGA开发指南

[复制链接]

出0入234汤圆

发表于 2020-9-23 10:08:38 | 显示全部楼层 |阅读模式
本帖最后由 正点原子 于 2020-9-23 10:22 编辑

1)实验平台:正点原子领航者ZYNQ开发板
2)购买链接:https://item.taobao.com/item.htm?id=606160108761
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz_linhanz.html
4) 正点原子官方B站:https://space.bilibili.com/394620890
5)对正点原子FPGA感兴趣的同学可以加群讨论:876744900 点击加入: QQ群头像.png                                                                                                
6)关注正点原子公众号,获取最新资料


100846rel79a9p4uelap24.jpg

100846f1ce1fg14zbg0va4.png
第二十五章以太网ARP测试实验



在以太网中,一个主机和另一个主机进行通信,必须要知道目的主机的MAC地址(物理地址),而目的MAC地址的获取由ARP协议完成。本章我们来学习如何通过领航者ZYNQ开发板实现ARP协议的功能。
本章分为以下几个章节:
2525.1简介
25.2实验任务
25.3硬件设计
25.4程序设计
25.5下载验证


25.1简介
ARP概述
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(逆地址解析协议)两种,如下图所示。
阿莫论坛发帖领航者专用2979.png

图 25.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请求的示意图如下图所示:
阿莫论坛发帖领航者专用21383.png

图 25.1.2 ARP请求示意图

上图中的主机A发起ARP请求,由于发送的目的MAC地址为广播地址,所以此时局域网中的所有主机都会进行接收并处理这个ARP请求报文,然后进行验证,查看接收方的IP地址是不是自己的地址。是则返回ARP应答报文,不是则不响应。
只有验证成功的主机才会返回一个ARP应答报文,这个应答报文包含接收方的IP地址和物理地址。ARP应答的示意图如下图所示:
阿莫论坛发帖领航者专用21618.png

图 25.1.3 ARP应答示意图

主机B利用收到的ARP请求报文中的请求方物理地址,以单播的方式直接发送给主机A,主机A将收到的ARP应答报文中的目的MAC地址解析出来,将目的MAC地址和目的IP地址更新至ARP缓存表中。当再次和主机A通信时,可以直接从ARP缓存表中获取,而不用重新发起ARP请求报文。需要说明的是,ARP缓存表中的表项有过期时间(一般为20分钟),过期之后,需要重新发起ARP请求以获取目的MAC地址。
ARP协议通过以太网进行传输,那么必须也要按照以太网所规定的格式进行传输,我们先来介绍下以太网的帧格式,随后再来向大家详细介绍ARP协议的具体格式。
以太网是目前应用最广泛的局域网通讯方式,同时也是一种协议。以太网协议定义了一系列软件和硬件标准,从而将不同的计算机设备连接在一起。我们知道串口通信单次只传输一个字节,而以太网通信是以数据包的形式传输,其单包数据量达到几十,甚至成百上千个字节。下图为以太网通过ARP传输单包数据的格式,从图中可以看出,以太网的数据包就是对协议的封装来实现数据的传输,即ARP数据位于以太网帧格式的数据段。这里只是让大家了解下以太网数据包的格式,后面会逐个展开来讲。
阿莫论坛发帖领航者专用22175.png

图 25.1.4 以太网ARP数据包格式

以太网MAC帧格式
以太网技术的正式标准是IEEE 802.3,它规定了以太网传输数据的帧结构,我们可以把以太网MAC层理解成高速公路,我们必须遵循它的规则才能在上面通行,以太网MAC层帧格式如图 25.1.5所示。
阿莫论坛发帖领航者专用22374.png

图 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,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最小时间为: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数据包格式如下图所示。
阿莫论坛发帖领航者专用24269.png

图 25.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报文的格式如下图所示:
阿莫论坛发帖领航者专用24847.png

图 25.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布线时需要尽可能对时钟、控制和数据线进行等长处理,且时序约束相对也更为严格。
为了节省引脚,领航者ZYNQ开发板板载的PHY芯片采用的接口为RGMII接口,下图是MAC侧与PHY侧接口的连接。
阿莫论坛发帖领航者专用25701.png

图 25.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发送端口正常模式时序图如下:
阿莫论坛发帖领航者专用27168.png

图 25.1.9 RGMII发送端口正常模式

由上图可知,RGMII发送端口正常模式下,需要满足TXC的上下边沿与TXD和TX_CTL信号的中间位置对齐,TXC的时钟周期为8ns,单个高电平或者低电平为4ns,TXC相对于TXD和TX_CTL延时约2ns。
RGMII发送端口延时模式时序图如下:
阿莫论坛发帖领航者专用27362.png

图 25.1.10 RGMII发送端口延时模式

由上图可知,RGMII发送端口延时模式下,需要满足TXC的上下边沿与TXD和TX_CTL信号对齐,相位相同。B50610 RGMII发送端口默认采用延时模式,其模式由MDIO接口进行配置,如下图 25.1.11所示。其中寄存器地址0x1C的Bit[9]用于配置发送端口的延时模式,默认配置为1,即采用延时模式。
阿莫论坛发帖领航者专用27611.png

图 25.1.11 RGMII发送端口模式配置

RGMII接收端口正常模式时序图如下:
阿莫论坛发帖领航者专用27700.png

图 25.1.12 RGMII接收端口正常模式

RGMII接收端口正常模式下,RXC的上下边沿与RXD和RX_CTL信号对齐,相位相同。
RGMII接收端口延时模式时序图如下:
阿莫论坛发帖领航者专用27834.png

图 25.1.13 RGMII接收端口延时模式

RGMII接收端口延时模式下,RXC的上下边沿与RXD和RX_CTL信号的中间位置对齐,RXC的时钟周期为8ns,单个高电平或者低电平为4ns,RXC相对于RXD和RX_CTL延时约2ns。B50610 RGMII接收端口默认采用延时模式,其模式由MDIO接口进行配置,如下图 25.1.14所示。其中寄存器地址0x18的Bit[8]用于配置发送端口的延时模式,默认配置为1,即采用延时模式。
阿莫论坛发帖领航者专用28124.png

图 25.1.14 RGMII接收端口模式配置

由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原语模板如下:
  1. BUFG BUFG_inst (
  2.     .O(O), // 1-bit output: Clock output
  3.     .I(I)  // 1-bit input: Clock input
  4. );
复制代码

除了BUFG外,常用的还有BUFR,BUFR是regional时钟网络,它的驱动范围只能局限在一个clock region的逻辑。BUFR相比BUFG的最大优势是偏斜和功耗都比较小。
BUFIO:BUFIO是IO时钟网络,其独立于全局时钟资源,适合采集源同步数据。它只能驱动IO Block里面的逻辑,不能驱动CLB里面的LUT,REG等逻辑。BUFIO原语模板如下:
  1. BUFIO BUFIO_inst (
  2.     .O(O), // 1-bit output: Clock output (connect to I/O clock loads).
  3.     .I(I)  // 1-bit input: Clock input (connect to an IBUF or BUFMR).
  4.     );
复制代码

BUFIO在采集源同步IO数据时,提供非常小的延时,因此非常适合采集比如RGMII接收侧的数据,但是由于其不能驱动FPGA的内部逻辑,因此需要BUFIO和BUFG配合使用,以达到最佳性能。如ETH_RXC的时钟经过BUFIO,用来采集端口数据;ETH_RXC经过BUFG,用来作为除端口采集外的其他模块的操作时钟。
IDDR:在7系列设备的ILOGIC block中有专属的registers来实现input double-data-rate(IDDR) registers,将输入的上下边沿DDR信号,转换成两位单边沿SDR信号。IDDR的原语结构图如下图所示:
阿莫论坛发帖领航者专用29428.png

图 25.1.15 IDDR原语结构图

C:输入的同步时钟;
D:输入的1位DDR数据;
Q1和Q2:分别是“C”时钟上升沿和下降沿同步输出的SDR数据。
CE:时钟使能信号;
S/R:置位/复位信号,这两个信号不能同时拉高。
IDDR原语模板如下:
  1. IDDR #(
  2.       .DDR_CLK_EDGE("OPPOSITE_EDGE"), // "OPPOSITE_EDGE", "SAME_EDGE"
  3.                                       //    or "SAME_EDGE_PIPELINED"
  4.       .INIT_Q1(1'b0), // Initial value of Q1: 1'b0 or 1'b1
  5.       .INIT_Q2(1'b0), // Initial value of Q2: 1'b0 or 1'b1
  6.       .SRTYPE("SYNC") // Set/Reset type: "SYNC" or "ASYNC"
  7.    ) IDDR_inst (
  8.       .Q1(Q1), // 1-bit output for positive edge of clock
  9.       .Q2(Q2), // 1-bit output for negative edge of clock
  10.       .C(C),   // 1-bit clock input
  11.       .CE(CE), // 1-bit clock enable input
  12.       .D(D),   // 1-bit DDR data input
  13.       .R(R),   // 1-bit reset
  14.       .S(S)    // 1-bit set
  15.    );
复制代码

DDR_CLK_EDGE参数为IDDR的三种采集模式,分别为“OPPOSITE_EDGE”、“SAME_EDGE”和“SAME_EDGE_PIPELINED”模式。
OPPOSITE_EDGE模式的时序图如下图所示:
阿莫论坛发帖领航者专用210350.png

图 25.1.16 IDDR“OPPOSITE_EDGE”模式时序图

OPPOSITE_EDGE模式下,在时钟的上升沿输出的Q1,时钟的下降沿输出Q2。
SAME_EDGE模式的时序图如下图所示:
阿莫论坛发帖领航者专用210494.png

图 25.1.17 IDDR“SAME_EDGE”模式时序图

SAME_EDGE模式下,在时钟的上升沿输出Q1和Q2,但Q1和Q2不在同一个cycle输出。
SAME_EDGE_PIPELINED模式的时序图如下图所示:
阿莫论坛发帖领航者专用210650.png

图 25.1.18 IDDR“SAME_EDGE_PIPELINED”模式时序图

SAME_EDGE_PIPELINED模式下,在时钟的上升沿输出Q1和Q2,Q1和Q2虽然在同一个cycle输出,但整体延时了一个时钟周期。在使用IDDR时,一般采用此种模式。
ODDR:通过ODDR把两路单端的数据合并到一路上输出,上下沿同时输出数据,上升沿输出a路,下降沿输出b路;如果两路输入信号一路固定为1,另外一路固定为0,那么输出的信号实际上是时钟信号。
ODDR的原语结构图如下图所示:
阿莫论坛发帖领航者专用210937.png

图 25.1.19 ODDR原语结构图

C:输入的同步时钟;
Q:输出的1位DDR数据;
D1和D2:分别是“C”时钟上升沿和下降沿同步输入的SDR数据。
CE:时钟使能信号;
S/R:置位/复位信号,这两个信号不能同时拉高。
ODDR原语模板如下:
  1. ODDR #(
  2.       .DDR_CLK_EDGE("OPPOSITE_EDGE"), // "OPPOSITE_EDGE" or "SAME_EDGE"
  3.       .INIT(1'b0),    // Initial value of Q: 1'b0 or 1'b1
  4.       .SRTYPE("SYNC") // Set/Reset type: "SYNC" or "ASYNC"
  5.    ) ODDR_inst (
  6.       .Q(Q),   // 1-bit DDR output
  7.       .C(C),   // 1-bit clock input
  8.       .CE(CE), // 1-bit clock enable input
  9.       .D1(D1), // 1-bit data input (positive edge)
  10.       .D2(D2), // 1-bit data input (negative edge)
  11.       .R(R),   // 1-bit reset
  12.       .S(S)    // 1-bit set
  13.    );
复制代码

DDR_CLK_EDGE参数为ODDR的两种输出模式,分别为“OPPOSITE_EDGE”和“SAME_EDGE”模式。
OPPOSITE_EDGE模式的时序图如下图所示:
阿莫论坛发帖领航者专用211692.png

图 25.1.20 ODDR OPPOSITE_EDGE模式时序图

此种模式下,在FPGA内部需要两个反相时钟来同步D1和D2,此种模式使用较少。
SAME_EDGE模式的时序图如下图所示:
阿莫论坛发帖领航者专用211833.png

图 25.1.21 ODDR SAME_EDGE模式时序图

此种模式下,数据可以在相同的时钟边沿输出到Q,一般采用此种模式。
IDELAYE2:IO延时原语,用于在信号通过引脚进入芯片内部之前,进行延时调节,一般高速端口信号由于走线延时等原因,需要通过IDELAYE2原语对数据做微调。IDELAYE2原语模板如下:
  1. IDELAYE2 #(
  2.       .CINVCTRL_SEL("FALSE"),          // Enable dynamic clock inversion (FALSE, TRUE)
  3.       .DELAY_SRC("IDATAIN"),           // Delay input (IDATAIN, DATAIN)
  4.       .HIGH_PERFORMANCE_MODE("FALSE"), // Reduced jitter ("TRUE"), Reduced power ("FALSE")
  5.       .IDELAY_TYPE("FIXED"),           // FIXED, VARIABLE, VAR_LOAD, VAR_LOAD_PIPE
  6.       .IDELAY_VALUE(0),                // Input delay tap setting (0-31)
  7.       .PIPE_SEL("FALSE"),              // Select pipelined mode, FALSE, TRUE
  8.       .REFCLK_FREQUENCY(200.0),        // IDELAYCTRL clock input frequency in MHz
  9.       .SIGNAL_PATTERN("DATA")          // DATA, CLOCK input signal
  10.    )
  11.    IDELAYE2_inst (
  12.       .CNTVALUEOUT(CNTVALUEOUT), // 5-bit output: Counter value output
  13.       .DATAOUT(DATAOUT),         // 1-bit output: Delayed data output
  14.       .C(C),                     // 1-bit input: Clock input
  15.       .CE(CE),                   // 1-bit input: Active high enable increment/decrement input
  16.       .CINVCTRL(CINVCTRL),       // 1-bit input: Dynamic clock inversion input
  17.       .CNTVALUEIN(CNTVALUEIN),   // 5-bit input: Counter value input
  18.       .DATAIN(DATAIN),           // 1-bit input: Internal delay data input
  19.       .IDATAIN(IDATAIN),         // 1-bit input: Data input from the I/O
  20.       .INC(INC),                 // 1-bit input: Increment / Decrement tap delay input
  21.       .LD(LD),                   // 1-bit input: Load IDELAY_VALUE input
  22.       .LDPIPEEN(LDPIPEEN),       // 1-bit input: Enable PIPELINE register to load data input
  23.       .REGRST(REGRST)            // 1-bit input: Active-high reset tap-delay input
  24.    );  
复制代码

IDATAIN为延时前的输入信号,DATAOUT为延时后的输出信号。
REFCLK_FREQUENCY参数为IDELAYCTRL原语的参考时钟频率,一般为200Mhz; IDELAY_VALUE参数用来设置延时的tap数,范围为1~31,每个tap数的延时时间和参考时钟频率有关。
和IDELAYE2对应的还有ODELAYE2,由于A7系列没有ODELAYE2原语,故此处不做讨论。
IDELAYCTRL:IDELAYCTRL和IDELAYE2一般同时使用,IDELAYCTRL对IDELAYE2延时进行校准。IDELAYE2原语如下:
  1. (* IODELAY_GROUP = <iodelay_group_name> *)

  2. IDELAYCTRL IDELAYCTRL_inst (
  3.       .RDY(RDY),       // 1-bit output: Ready output
  4.       .REFCLK(REFCLK), // 1-bit input: Reference clock input
  5.       .RST(RST)        // 1-bit input: Active high reset input
  6.    );  
复制代码

IODELAY_GROUP为延时IO分组,一般数据接口位于多个BANK时,才需要分组。
IDELAYCTRL通过参考时钟REFCLK来校准IDELAY2每个tap的延时值,可用的REFCLK频率为190Mhz~210Mhz或者290Mhz~310Mhz。时钟频率越高对应的tap延时平均值越小,即延时调节精度越高。当参考时钟为200Mhz时,一个tap为78ps。
25.2实验任务
本节实验任务是使用领航者ZYNQ开发板上的PL端以太网接口,和上位机实现ARP请求和应答的功能。当上位机发送ARP请求时,开发板返回ARP应答数据。当按下开发板的触摸按键时,开发板发送ARP请求,此时上位机返回应答数据。
25.3硬件设计
PL端千兆以太网接口部分的硬件设计原理和“MDIO接口读写测试实验” 完全相同,请参考“MDIO接口读写测试实验”中的硬件设计部分。
本实验中,各端口信号的管脚分配如下表所示:
表 25.3.1 以太网ARP测试实验管脚分配
2531.png

对应的XDC约束语句如下所示:
  1. create_clock -period 20.000 -name sys_clk [get_ports sys_clk]
  2. create_clock -period 8.000 -name eth_rxc [get_ports eth_rxc]

  3. set_property -dict {PACKAGE_PIN U18 IOSTANDARD LVCMOS33} [get_ports sys_clk]
  4. set_property -dict {PACKAGE_PIN J15 IOSTANDARD LVCMOS33} [get_ports sys_rst_n]

  5. set_property -dict {PACKAGE_PIN L19 IOSTANDARD LVCMOS33} [get_ports touch_key]
  6. #GE_PL
  7. set_property -dict {PACKAGE_PIN G14 IOSTANDARD LVCMOS33} [get_ports eth_rst_n]
  8. set_property -dict {PACKAGE_PIN K17 IOSTANDARD LVCMOS33} [get_ports eth_rxc]
  9. set_property -dict {PACKAGE_PIN D19 IOSTANDARD LVCMOS33} [get_ports eth_rx_ctl]
  10. set_property -dict {PACKAGE_PIN F19 IOSTANDARD LVCMOS33} [get_ports {eth_rxd[0]}]
  11. set_property -dict {PACKAGE_PIN F20 IOSTANDARD LVCMOS33} [get_ports {eth_rxd[1]}]
  12. set_property -dict {PACKAGE_PIN E17 IOSTANDARD LVCMOS33} [get_ports {eth_rxd[2]}]
  13. set_property -dict {PACKAGE_PIN D18 IOSTANDARD LVCMOS33} [get_ports {eth_rxd[3]}]

  14. set_property -dict {PACKAGE_PIN G19 IOSTANDARD LVCMOS33} [get_ports eth_txc]
  15. set_property -dict {PACKAGE_PIN E19 IOSTANDARD LVCMOS33} [get_ports eth_tx_ctl]
  16. set_property -dict {PACKAGE_PIN G20 IOSTANDARD LVCMOS33} [get_ports {eth_txd[0]}]
  17. set_property -dict {PACKAGE_PIN F16 IOSTANDARD LVCMOS33} [get_ports {eth_txd[1]}]
  18. set_property -dict {PACKAGE_PIN F17 IOSTANDARD LVCMOS33} [get_ports {eth_txd[2]}]
  19. set_property -dict {PACKAGE_PIN E18 IOSTANDARD LVCMOS33} [get_ports {eth_txd[3]}]
复制代码

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

图 25.4.1 以太网ARP测试系统框图

系统时钟经过PLL时钟模块后,输出200Mhz的时钟,用于IDELAYCTRL原语的参考时钟;GMII TO RGMII模块负责将双沿(DDR)数据和单沿(SDR)数据之间的转换;ARP顶层模块实现了以太网ARP数据包的接收、发送以及CRC校验的功能;ARP控制模块根据输入的按键触摸信号和接收到的ARP请求信号,控制ARP顶层模块发送ARP请求或者ARP应答。
各模块端口及信号连接如下图所示:
阿莫论坛发帖领航者专用217256.png

图 25.4.2 顶层模块原理图

由上图可知,FPGA顶层模块例化了以下四个模块,PLL时钟模块(clk_wiz)、GMII TO RGMII模块(gmii_to_rgmii)、ARP顶层模块(arp)和ARP控制模块(arp_ctrl),实现了各模块之间的数据交互。
其中ARP顶层模块和GMII TO RGMII模块内部也例化了多个其它模块,这样设计的目的是为了方便模块的重用。
顶层模块的代码如下:
  1. 1   module eth_arp_test(
  2. 2       input              sys_clk   , //系统时钟
  3. 3       input              sys_rst_n , //系统复位信号,低电平有效
  4. 4       input              touch_key , //触摸按键,用于触发开发板发出ARP请求
  5. 5       //PL以太网RGMII接口   
  6. 6       input              eth_rxc   , //RGMII接收数据时钟
  7. 7       input              eth_rx_ctl, //RGMII输入数据有效信号
  8. 8       input       [3:0]  eth_rxd   , //RGMII输入数据
  9. 9       output             eth_txc   , //RGMII发送数据时钟   
  10. 10      output             eth_tx_ctl, //RGMII输出数据有效信号
  11. 11      output      [3:0]  eth_txd   , //RGMII输出数据         
  12. 12      output             eth_rst_n   //以太网芯片复位信号,低电平有效   
  13. 13      );
  14. 14  
  15. 15  //parameter define
  16. 16  //开发板MAC地址 00-11-22-33-44-55
  17. 17  parameter  BOARD_MAC = 48'h00_11_22_33_44_55;     
  18. 18  //开发板IP地址 192.168.1.10     
  19. 19  parameter  BOARD_IP  = {8'd192,8'd168,8'd1,8'd10};
  20. 20  //目的MAC地址 ff_ff_ff_ff_ff_ff
  21. 21  parameter  DES_MAC   = 48'hff_ff_ff_ff_ff_ff;
  22. 22  //目的IP地址 192.168.1.102
  23. 23  parameter  DES_IP    = {8'd192,8'd168,8'd1,8'd102};
  24. 24  //输入数据IO延时(如果为n,表示延时n*78ps)
  25. 25  parameter IDELAY_VALUE = 16;
  26. 26  
  27. 27  //wire define
  28. 28  wire          clk_200m   ; //用于IO延时的时钟
  29. 29               
  30. 30  wire          gmii_rx_clk; //GMII接收时钟
  31. 31  wire          gmii_rx_dv ; //GMII接收数据有效信号
  32. 32  wire  [7:0]   gmii_rxd   ; //GMII接收数据
  33. 33  wire          gmii_tx_clk; //GMII发送时钟
  34. 34  wire          gmii_tx_en ; //GMII发送数据使能信号
  35. 35  wire  [7:0]   gmii_txd   ; //GMII发送数据
  36. 36               
  37. 37  wire          arp_rx_done; //ARP接收完成信号
  38. 38  wire          arp_rx_type; //ARP接收类型 0:请求  1:应答
  39. 39  wire  [47:0]  src_mac    ; //接收到目的MAC地址
  40. 40  wire  [31:0]  src_ip     ; //接收到目的IP地址   
  41. 41  wire          arp_tx_en  ; //ARP发送使能信号
  42. 42  wire          arp_tx_type; //ARP发送类型 0:请求  1:应答
  43. 43  wire          tx_done    ; //发送的目标MAC地址
  44. 44  wire  [47:0]  des_mac    ; //发送的目标IP地址
  45. 45  wire  [31:0]  des_ip     ; //以太网发送完成信号   
  46. 46  
  47. 47  //*****************************************************
  48. 48  //**                    main code
  49. 49  //*****************************************************
  50. 50  
  51. 51  assign des_mac = src_mac;
  52. 52  assign des_ip = src_ip;
  53. 53  assign eth_rst_n = sys_rst_n;
  54. 54  
  55. 55  //MMCM/PLL
  56. 56  clk_wiz u_clk_wiz
  57. 57  (
  58. 58      .clk_in1   (sys_clk   ),
  59. 59      .clk_out1  (clk_200m  ),   
  60. 60      .reset     (~sys_rst_n),
  61. 61      .locked    (locked)
  62. 62  );
  63. 63  
  64. 64  //GMII接口转RGMII接口
  65. 65  gmii_to_rgmii
  66. 66      #(
  67. 67       .IDELAY_VALUE (IDELAY_VALUE)
  68. 68       )
  69. 69      u_gmii_to_rgmii(
  70. 70      .idelay_clk    (clk_200m    ),
  71. 71  
  72. 72      .gmii_rx_clk   (gmii_rx_clk ),
  73. 73      .gmii_rx_dv    (gmii_rx_dv  ),
  74. 74      .gmii_rxd      (gmii_rxd    ),
  75. 75      .gmii_tx_clk   (gmii_tx_clk ),
  76. 76      .gmii_tx_en    (gmii_tx_en  ),
  77. 77      .gmii_txd      (gmii_txd    ),
  78. 78      
  79. 79      .rgmii_rxc     (eth_rxc     ),
  80. 80      .rgmii_rx_ctl  (eth_rx_ctl  ),
  81. 81      .rgmii_rxd     (eth_rxd     ),
  82. 82      .rgmii_txc     (eth_txc     ),
  83. 83      .rgmii_tx_ctl  (eth_tx_ctl  ),
  84. 84      .rgmii_txd     (eth_txd     )
  85. 85      );
  86. 86  
  87. 87  //ARP通信
  88. 88  arp                                             
  89. 89     #(
  90. 90      .BOARD_MAC     (BOARD_MAC),      //参数例化
  91. 91      .BOARD_IP      (BOARD_IP ),
  92. 92      .DES_MAC       (DES_MAC  ),
  93. 93      .DES_IP        (DES_IP   )
  94. 94      )
  95. 95     u_arp(
  96. 96      .rst_n         (sys_rst_n  ),
  97. 97                     
  98. 98      .gmii_rx_clk   (gmii_rx_clk),
  99. 99      .gmii_rx_dv    (gmii_rx_dv ),
  100. 100     .gmii_rxd      (gmii_rxd   ),
  101. 101     .gmii_tx_clk   (gmii_tx_clk),
  102. 102     .gmii_tx_en    (gmii_tx_en ),
  103. 103     .gmii_txd      (gmii_txd   ),
  104. 104                     
  105. 105     .arp_rx_done   (arp_rx_done),
  106. 106     .arp_rx_type   (arp_rx_type),
  107. 107     .src_mac       (src_mac    ),
  108. 108     .src_ip        (src_ip     ),
  109. 109     .arp_tx_en     (arp_tx_en  ),
  110. 110     .arp_tx_type   (arp_tx_type),
  111. 111     .des_mac       (des_mac    ),
  112. 112     .des_ip        (des_ip     ),
  113. 113     .tx_done       (tx_done    )
  114. 114     );
  115. 115
  116. 116 //ARP控制
  117. 117 arp_ctrl u_arp_ctrl(
  118. 118     .clk           (gmii_rx_clk),
  119. 119     .rst_n         (sys_rst_n),
  120. 120                    
  121. 121     .touch_key     (touch_key),
  122. 122     .arp_rx_done   (arp_rx_done),
  123. 123     .arp_rx_type   (arp_rx_type),
  124. 124     .arp_tx_en     (arp_tx_en),
  125. 125     .arp_tx_type   (arp_tx_type)
  126. 126     );
  127. 127
  128. 128 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模块代码如下:
  1. 1  module gmii_to_rgmii(
  2. 2      input              idelay_clk  , //IDELAY时钟
  3. 3      //以太网GMII接口
  4. 4      output             gmii_rx_clk , //GMII接收时钟
  5. 5      output             gmii_rx_dv  , //GMII接收数据有效信号
  6. 6      output      [7:0]  gmii_rxd    , //GMII接收数据
  7. 7      output             gmii_tx_clk , //GMII发送时钟
  8. 8      input              gmii_tx_en  , //GMII发送数据使能信号
  9. 9      input       [7:0]  gmii_txd    , //GMII发送数据            
  10. 10     //以太网RGMII接口   
  11. 11     input              rgmii_rxc   , //RGMII接收时钟
  12. 12     input              rgmii_rx_ctl, //RGMII接收数据控制信号
  13. 13     input       [3:0]  rgmii_rxd   , //RGMII接收数据
  14. 14     output             rgmii_txc   , //RGMII发送时钟   
  15. 15     output             rgmii_tx_ctl, //RGMII发送数据控制信号
  16. 16     output      [3:0]  rgmii_txd     //RGMII发送数据         
  17. 17     );
  18. 18
  19. 19 //parameter define
  20. 20 parameter IDELAY_VALUE = 0;  //输入数据IO延时(如果为n,表示延时n*78ps)
  21. 21
  22. 22 //*****************************************************
  23. 23 //**                    main code
  24. 24 //*****************************************************
  25. 25
  26. 26 assign gmii_tx_clk = gmii_rx_clk;
  27. 27
  28. 28 //RGMII接收
  29. 29 rgmii_rx
  30. 30     #(
  31. 31      .IDELAY_VALUE  (IDELAY_VALUE)
  32. 32      )
  33. 33     u_rgmii_rx(
  34. 34     .idelay_clk    (idelay_clk),
  35. 35     .gmii_rx_clk   (gmii_rx_clk),
  36. 36     .rgmii_rxc     (rgmii_rxc   ),
  37. 37     .rgmii_rx_ctl  (rgmii_rx_ctl),
  38. 38     .rgmii_rxd     (rgmii_rxd   ),
  39. 39     
  40. 40     .gmii_rx_dv    (gmii_rx_dv ),
  41. 41     .gmii_rxd      (gmii_rxd   )
  42. 42     );
  43. 43
  44. 44 //RGMII发送
  45. 45 rgmii_tx u_rgmii_tx(
  46. 46     .gmii_tx_clk   (gmii_tx_clk ),
  47. 47     .gmii_tx_en    (gmii_tx_en  ),
  48. 48     .gmii_txd      (gmii_txd    ),
  49. 49               
  50. 50     .rgmii_txc     (rgmii_txc   ),
  51. 51     .rgmii_tx_ctl  (rgmii_tx_ctl),
  52. 52     .rgmii_txd     (rgmii_txd   )
  53. 53     );
  54. 54
  55. 55 endmodule
复制代码

由该模块的端口可知,该模块实现了双沿(DDR)数据和单沿(SDR)数据之间的转换。程序中第26行将GMII接收时钟赋值给GMII发送时钟,因此GMII的发送时钟和接收时钟实际上为同一个时钟。GMII TO RGMII模块例化了rgmii_rx模块和rgmii_tx模块。
rgmii_rx模块代码如下所示:
  1. 1   module rgmii_rx(
  2. 2       input              idelay_clk  , //200Mhz时钟,IDELAY时钟
  3. 3      
  4. 4       //以太网RGMII接口
  5. 5       input              rgmii_rxc   , //RGMII接收时钟
  6. 6       input              rgmii_rx_ctl, //RGMII接收数据控制信号
  7. 7       input       [3:0]  rgmii_rxd   , //RGMII接收数据   
  8. 8   
  9. 9       //以太网GMII接口
  10. 10      output             gmii_rx_clk , //GMII接收时钟
  11. 11      output             gmii_rx_dv  , //GMII接收数据有效信号
  12. 12      output      [7:0]  gmii_rxd      //GMII接收数据   
  13. 13      );
  14. 14  
  15. 15  //parameter define
  16. 16  parameter IDELAY_VALUE = 0;
  17. 17  
  18. 18  //wire define
  19. 19  wire         rgmii_rxc_bufg;     //全局时钟缓存
  20. 20  wire         rgmii_rxc_bufio;    //全局时钟IO缓存
  21. 21  wire  [3:0]  rgmii_rxd_delay;    //rgmii_rxd输入延时
  22. 22  wire         rgmii_rx_ctl_delay; //rgmii_rx_ctl输入延时
  23. 23  wire  [1:0]  gmii_rxdv_t;        //两位GMII接收有效信号
  24. 24  
  25. 25  //*****************************************************
  26. 26  //**                    main code
  27. 27  //*****************************************************
  28. 28  
  29. 29  assign gmii_rx_clk = rgmii_rxc_bufg;
  30. 30  assign gmii_rx_dv = gmii_rxdv_t[0] & gmii_rxdv_t[1];
  31. 31  
  32. 32  //全局时钟缓存
  33. 33  BUFG BUFG_inst (
  34. 34    .I            (rgmii_rxc),     // 1-bit input: Clock input
  35. 35    .O            (rgmii_rxc_bufg) // 1-bit output: Clock output
  36. 36  );
  37. 37  
  38. 38  //全局时钟IO缓存
  39. 39  BUFIO BUFIO_inst (
  40. 40    .I            (rgmii_rxc),      // 1-bit input: Clock input
  41. 41    .O            (rgmii_rxc_bufio) // 1-bit output: Clock output
  42. 42  );
  43. 43  
  44. 44  //输入延时控制
  45. 45  // Specifies group name for associated IDELAYs/ODELAYs and IDELAYCTRL
  46. 46  (* IODELAY_GROUP = "rgmii_rx_delay" *)
  47. 47  IDELAYCTRL  IDELAYCTRL_inst (
  48. 48      .RDY(),                      // 1-bit output: Ready output
  49. 49      .REFCLK(idelay_clk),         // 1-bit input: Reference clock input
  50. 50      .RST(1'b0)                   // 1-bit input: Active high reset input
  51. 51  );
  52. 52  
  53. 53  //rgmii_rx_ctl输入延时与双沿采样
  54. 54  (* IODELAY_GROUP = "rgmii_rx_delay" *)
  55. 55  IDELAYE2 #(
  56. 56    .IDELAY_TYPE     ("FIXED"),           // FIXED, VARIABLE, VAR_LOAD, VAR_LOAD_PIPE
  57. 57    .IDELAY_VALUE    (IDELAY_VALUE),      // Input delay tap setting (0-31)
  58. 58    .REFCLK_FREQUENCY(200.0)              // IDELAYCTRL clock input frequency in MHz
  59. 59  )
  60. 60  u_delay_rx_ctrl (
  61. 61    .CNTVALUEOUT     (),                  // 5-bit output: Counter value output
  62. 62    .DATAOUT         (rgmii_rx_ctl_delay),// 1-bit output: Delayed data output
  63. 63    .C               (1'b0),              // 1-bit input: Clock input
  64. 64    .CE              (1'b0),              // 1-bit input: enable increment/decrement
  65. 65    .CINVCTRL        (1'b0),              // 1-bit input: Dynamic clock inversion input
  66. 66    .CNTVALUEIN      (5'b0),              // 5-bit input: Counter value input
  67. 67    .DATAIN          (1'b0),              // 1-bit input: Internal delay data input
  68. 68    .IDATAIN         (rgmii_rx_ctl),      // 1-bit input: Data input from the I/O
  69. 69    .INC             (1'b0),              // 1-bit input: Increment / Decrement tap delay
  70. 70    .LD              (1'b0),              // 1-bit input: Load IDELAY_VALUE input
  71. 71    .LDPIPEEN        (1'b0),              // 1-bit input: Enable PIPELINE register
  72. 72    .REGRST          (1'b0)               // 1-bit input: Active-high reset tap-delay input
  73. 73  );
  74. 74  
  75. 75  //输入双沿采样寄存器
  76. 76  IDDR #(
  77. 77      .DDR_CLK_EDGE("SAME_EDGE_PIPELINED"),// "OPPOSITE_EDGE", "SAME_EDGE"
  78. 78                                          //    or "SAME_EDGE_PIPELINED"
  79. 79      .INIT_Q1  (1'b0),                   // Initial value of Q1: 1'b0 or 1'b1
  80. 80      .INIT_Q2  (1'b0),                   // Initial value of Q2: 1'b0 or 1'b1
  81. 81      .SRTYPE   ("SYNC")                  // Set/Reset type: "SYNC" or "ASYNC"
  82. 82  ) u_iddr_rx_ctl (
  83. 83      .Q1       (gmii_rxdv_t[0]),         // 1-bit output for positive edge of clock
  84. 84      .Q2       (gmii_rxdv_t[1]),         // 1-bit output for negative edge of clock
  85. 85      .C        (rgmii_rxc_bufio),        // 1-bit clock input
  86. 86      .CE       (1'b1),                   // 1-bit clock enable input
  87. 87      .D        (rgmii_rx_ctl_delay),     // 1-bit DDR data input
  88. 88      .R        (1'b0),                   // 1-bit reset
  89. 89      .S        (1'b0)                    // 1-bit set
  90. 90  );
  91. 91  
  92. 92  //rgmii_rxd输入延时与双沿采样
  93. 93  genvar i;
  94. 94  generate for (i=0; i<4; i=i+1)
  95. 95      (* IODELAY_GROUP = "rgmii_rx_delay" *)
  96. 96      begin : rxdata_bus
  97. 97          //输入延时           
  98. 98          (* IODELAY_GROUP = "rgmii_rx_delay" *)
  99. 99          IDELAYE2 #(
  100. 100           .IDELAY_TYPE     ("FIXED"),           // FIXED,VARIABLE,VAR_LOAD,VAR_LOAD_PIPE
  101. 101           .IDELAY_VALUE    (IDELAY_VALUE),      // Input delay tap setting (0-31)   
  102. 102           .REFCLK_FREQUENCY(200.0)              // IDELAYCTRL clock input frequency in MHz
  103. 103         )
  104. 104         u_delay_rxd (
  105. 105           .CNTVALUEOUT     (),                  // 5-bit output: Counter value output
  106. 106           .DATAOUT         (rgmii_rxd_delay),// 1-bit output: Delayed data output
  107. 107           .C               (1'b0),              // 1-bit input: Clock input
  108. 108           .CE              (1'b0),              // 1-bit input: enable increment/decrement
  109. 109           .CINVCTRL        (1'b0),              // 1-bit input: Dynamic clock inversion
  110. 110           .CNTVALUEIN      (5'b0),              // 5-bit input: Counter value input
  111. 111           .DATAIN          (1'b0),              // 1-bit input: Internal delay data input
  112. 112           .IDATAIN         (rgmii_rxd),      // 1-bit input: Data input from the I/O
  113. 113           .INC             (1'b0),              // 1-bit input: Inc/Decrement tap delay
  114. 114           .LD              (1'b0),              // 1-bit input: Load IDELAY_VALUE input
  115. 115           .LDPIPEEN        (1'b0),              // 1-bit input: Enable PIPELINE register
  116. 116           .REGRST          (1'b0)               // 1-bit input: Active-high reset tap-delay
  117. 117         );
  118. 118         
  119. 119         //输入双沿采样寄存器
  120. 120         IDDR #(
  121. 121             .DDR_CLK_EDGE("SAME_EDGE_PIPELINED"),// "OPPOSITE_EDGE", "SAME_EDGE"
  122. 122                                                 //    or "SAME_EDGE_PIPELINED"
  123. 123             .INIT_Q1  (1'b0),                   // Initial value of Q1: 1'b0 or 1'b1
  124. 124             .INIT_Q2  (1'b0),                   // Initial value of Q2: 1'b0 or 1'b1
  125. 125             .SRTYPE   ("SYNC")                  // Set/Reset type: "SYNC" or "ASYNC"
  126. 126         ) u_iddr_rxd (
  127. 127             .Q1       (gmii_rxd),            // 1-bit output for positive edge of clock
  128. 128             .Q2       (gmii_rxd[4+i]),          // 1-bit output for negative edge of clock
  129. 129             .C        (rgmii_rxc_bufio),        // 1-bit clock input rgmii_rxc_bufio
  130. 130             .CE       (1'b1),                   // 1-bit clock enable input
  131. 131             .D        (rgmii_rxd_delay),     // 1-bit DDR data input
  132. 132             .R        (1'b0),                   // 1-bit reset
  133. 133             .S        (1'b0)                    // 1-bit set
  134. 134         );
  135. 135     end
  136. 136 endgenerate
  137. 137
  138. 138 endmodule
复制代码

该模块通过调用BUFG、BUFIO、IDDR、IDELAYCTRL和IDELAY2原语,实现了RGMII接口输入的DDR数据到SDR数据的转换,输入的rgmii_rx_ctl控制信号的转换方法同样类似。rgmii_rx模块信号转换示意图如下图所示:
阿莫论坛发帖领航者专用231304.png

图 25.4.3 rgmii_rx模块信号转换示意图

时钟专用引脚输入的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行代码通过generate for语句实现对IDELAY2和IDDR的例化,由于输入的数据引脚为4位数据,因此这里共例化了4次。其等效于分别对输入的IDELAY2和IDDR例化4次,这里采用generate for的写法可以减少很多的代码量,当需要对某个模块例化较多次数时,这种写法能够大大提高效率。
rgmii_tx模块代码如下所示:
  1. 1  module rgmii_tx(
  2. 2      //GMII发送端口
  3. 3      input              gmii_tx_clk , //GMII发送时钟   
  4. 4      input              gmii_tx_en  , //GMII输出数据有效信号
  5. 5      input       [7:0]  gmii_txd    , //GMII输出数据        
  6. 6      
  7. 7      //RGMII发送端口
  8. 8      output             rgmii_txc   , //RGMII发送数据时钟   
  9. 9      output             rgmii_tx_ctl, //RGMII输出数据有效信号
  10. 10     output      [3:0]  rgmii_txd     //RGMII输出数据     
  11. 11     );
  12. 12
  13. 13 //*****************************************************
  14. 14 //**                    main code
  15. 15 //*****************************************************
  16. 16
  17. 17 assign rgmii_txc = gmii_tx_clk;
  18. 18
  19. 19 //输出双沿采样寄存器 (rgmii_tx_ctl)
  20. 20 ODDR #(
  21. 21     .DDR_CLK_EDGE  ("SAME_EDGE"),  // "OPPOSITE_EDGE" or "SAME_EDGE"
  22. 22     .INIT          (1'b0),         // Initial value of Q: 1'b0 or 1'b1
  23. 23     .SRTYPE        ("SYNC")        // Set/Reset type: "SYNC" or "ASYNC"
  24. 24 ) ODDR_inst (
  25. 25     .Q             (rgmii_tx_ctl), // 1-bit DDR output
  26. 26     .C             (gmii_tx_clk),  // 1-bit clock input
  27. 27     .CE            (1'b1),         // 1-bit clock enable input
  28. 28     .D1            (gmii_tx_en),   // 1-bit data input (positive edge)
  29. 29     .D2            (gmii_tx_en),   // 1-bit data input (negative edge)
  30. 30     .R             (1'b0),         // 1-bit reset
  31. 31     .S             (1'b0)          // 1-bit set
  32. 32 );
  33. 33
  34. 34 genvar i;
  35. 35 generate for (i=0; i<4; i=i+1)
  36. 36     begin : txdata_bus
  37. 37         //输出双沿采样寄存器 (rgmii_txd)
  38. 38         ODDR #(
  39. 39             .DDR_CLK_EDGE  ("SAME_EDGE"),  // "OPPOSITE_EDGE" or "SAME_EDGE"
  40. 40             .INIT          (1'b0),         // Initial value of Q: 1'b0 or 1'b1
  41. 41             .SRTYPE        ("SYNC")        // Set/Reset type: "SYNC" or "ASYNC"
  42. 42         ) ODDR_inst (
  43. 43             .Q             (rgmii_txd), // 1-bit DDR output
  44. 44             .C             (gmii_tx_clk),  // 1-bit clock input
  45. 45             .CE            (1'b1),         // 1-bit clock enable input
  46. 46             .D1            (gmii_txd),  // 1-bit data input (positive edge)
  47. 47             .D2            (gmii_txd[4+i]),// 1-bit data input (negative edge)
  48. 48             .R             (1'b0),         // 1-bit reset
  49. 49             .S             (1'b0)          // 1-bit set
  50. 50         );        
  51. 51     end
  52. 52 endgenerate
  53. 53
  54. 54 endmodule
复制代码

该模块通过调用ODDR原语将输入的单沿8位数据(gmii_txd)转换成双沿采样的4位数据(rgmii_txd),gmii_tx_en和rgmii_tx_ctl信号的处理方法同样类似。rgmii_tx模块信号转换示意图如下图所示:
阿莫论坛发帖领航者专用234229.png

图 25.4.4 rgmii_tx模块信号转换示意图

gmii_tx_en数据使能信号和8位gmii_txd数据经过ODDR将单沿2位数据转换成双沿1位数据。需要说明的是,在程序的第34行至第52行同样通过generate for语句实现原语的多次例化。
ARP顶层模块实现了整个以太网帧格式与ARP协议的功能,其模块端口及信号连接如下图所示:
阿莫论坛发帖领航者专用234447.png

图 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接收模块通过状态机来解析数据,其状态跳转图如下图所示:
阿莫论坛发帖领航者专用235418.png

图 25.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数据状态和接收结束状态源代码,代码如下:
  1. 185             st_arp_data : begin
  2. 186                 if(gmii_rx_dv) begin
  3. 187                     cnt <= cnt + 5'd1;
  4. 188                     if(cnt == 5'd6)
  5. 189                         op_data[15:8] <= gmii_rxd;           //操作码      
  6. 190                     else if(cnt == 5'd7)
  7. 191                         op_data[7:0] <= gmii_rxd;
  8. 192                     else if(cnt >= 5'd8 && cnt < 5'd14)      //源MAC地址
  9. 193                         src_mac_t <= {src_mac_t[39:0],gmii_rxd};
  10. 194                     else if(cnt >= 5'd14 && cnt < 5'd18)     //源IP地址
  11. 195                         src_ip_t<= {src_ip_t[23:0],gmii_rxd};
  12. 196                     else if(cnt >= 5'd24 && cnt < 5'd28)     //目标IP地址
  13. 197                         des_ip_t <= {des_ip_t[23:0],gmii_rxd};
  14. 198                     else if(cnt == 5'd28) begin
  15. 199                         cnt <= 5'd0;
  16. 200                         if(des_ip_t == BOARD_IP) begin       //判断目的IP地址和操作码
  17. 201                             if((op_data == 16'd1) || (op_data == 16'd2)) begin
  18. 202                                 skip_en <= 1'b1;
  19. 203                                 arp_rx_done <= 1'b1;
  20. 204                                 src_mac <= src_mac_t;
  21. 205                                 src_ip <= src_ip_t;
  22. 206                                 src_mac_t <= 48'd0;
  23. 207                                 src_ip_t <= 32'd0;
  24. 208                                 des_mac_t <= 48'd0;
  25. 209                                 des_ip_t <= 32'd0;
  26. 210                                 if(op_data == 16'd1)         
  27. 211                                     arp_rx_type <= 1'b0;     //ARP请求
  28. 212                                 else
  29. 213                                     arp_rx_type <= 1'b1;     //ARP应答
  30. 214                             end
  31. 215                             else
  32. 216                                 error_en <= 1'b1;
  33. 217                         end
  34. 218                         else
  35. 219                             error_en <= 1'b1;
  36. 220                     end
  37. 221                 end                                
  38. 222             end
  39. 223             st_rx_end : begin     
  40. 224                 cnt <= 5'd0;
  41. 225                 //单包数据接收完成   
  42. 226                 if(gmii_rx_dv == 1'b0 && skip_en == 1'b0)
  43. 227                     skip_en <= 1'b1;
  44. 228             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请求数据包。
阿莫论坛发帖领航者专用238505.png

图 25.4.7 ARP接收采集ILA波形图

ARP发送模块则是根据以太网帧格式是ARP协议发送数据,也就是接收模块的逆过程,同样也非常适合使用状态机来完成发送数据的功能,状态跳转图如下图所示:
阿莫论坛发帖领航者专用238648.png

图 25.4.8 ARP发送模块状态跳转图

发送模块和接收模块有很多相似之处,同样使用三段式状态机来发送以太网包,从上图可以比较直观的看到每个状态实现的功能以及跳转到下一个状态的条件。
发送模块的代码中定义了数组来存储前导码+帧头、以太网的帧头、ARP数据,在复位时初始化数组的值,部分源代码如下。
  1. 66  reg  [7:0]  preamble[7:0] ; //前导码+SFD
  2. 67  reg  [7:0]  eth_head[13:0]; //以太网首部
  3. 68  reg  [7:0]  arp_data[27:0]; //ARP数据
复制代码

省略部分代码……
  1. 155         //初始化数组   
  2. 156         //前导码 7个8'h55 + 1个8'hd5
  3. 157         preamble[0] <= 8'h55;               
  4. 158         preamble[1] <= 8'h55;
  5. 159         preamble[2] <= 8'h55;
  6. 160         preamble[3] <= 8'h55;
  7. 161         preamble[4] <= 8'h55;
  8. 162         preamble[5] <= 8'h55;
  9. 163         preamble[6] <= 8'h55;
  10. 164         preamble[7] <= 8'hd5;
  11. 165         //以太网帧头
  12. 166         eth_head[0] <= DES_MAC[47:40];      //目的MAC地址
  13. 167         eth_head[1] <= DES_MAC[39:32];
  14. 168         eth_head[2] <= DES_MAC[31:24];
  15. 169         eth_head[3] <= DES_MAC[23:16];
  16. 170         eth_head[4] <= DES_MAC[15:8];
  17. 171         eth_head[5] <= DES_MAC[7:0];        
  18. 172         eth_head[6] <= BOARD_MAC[47:40];    //源MAC地址
  19. 173         eth_head[7] <= BOARD_MAC[39:32];   
  20. 174         eth_head[8] <= BOARD_MAC[31:24];   
  21. 175         eth_head[9] <= BOARD_MAC[23:16];   
  22. 176         eth_head[10] <= BOARD_MAC[15:8];   
  23. 177         eth_head[11] <= BOARD_MAC[7:0];     
  24. 178         eth_head[12] <= ETH_TYPE[15:8];     //以太网帧类型
  25. 179         eth_head[13] <= ETH_TYPE[7:0];      
  26. 180         //ARP数据                           
  27. 181         arp_data[0] <= HD_TYPE[15:8];       //硬件类型
  28. 182         arp_data[1] <= HD_TYPE[7:0];
  29. 183         arp_data[2] <= PROTOCOL_TYPE[15:8]; //上层协议类型
  30. 184         arp_data[3] <= PROTOCOL_TYPE[7:0];
  31. 185         arp_data[4] <= 8'h06;               //硬件地址长度,6
  32. 186         arp_data[5] <= 8'h04;               //协议地址长度,4
  33. 187         arp_data[6] <= 8'h00;               //OP,操作码 8'h01:ARP请求 8'h02:ARP应答
  34. 188         arp_data[7] <= 8'h01;
  35. 189         arp_data[8] <= BOARD_MAC[47:40];    //发送端(源)MAC地址
  36. 190         arp_data[9] <= BOARD_MAC[39:32];
  37. 191         arp_data[10] <= BOARD_MAC[31:24];
  38. 192         arp_data[11] <= BOARD_MAC[23:16];
  39. 193         arp_data[12] <= BOARD_MAC[15:8];
  40. 194         arp_data[13] <= BOARD_MAC[7:0];
  41. 195         arp_data[14] <= BOARD_IP[31:24];    //发送端(源)IP地址
  42. 196         arp_data[15] <= BOARD_IP[23:16];
  43. 197         arp_data[16] <= BOARD_IP[15:8];
  44. 198         arp_data[17] <= BOARD_IP[7:0];
  45. 199         arp_data[18] <= DES_MAC[47:40];     //接收端(目的)MAC地址
  46. 200         arp_data[19] <= DES_MAC[39:32];
  47. 201         arp_data[20] <= DES_MAC[31:24];
  48. 202         arp_data[21] <= DES_MAC[23:16];
  49. 203         arp_data[22] <= DES_MAC[15:8];
  50. 204         arp_data[23] <= DES_MAC[7:0];  
  51. 205         arp_data[24] <= DES_IP[31:24];      //接收端(目的)IP地址
  52. 206         arp_data[25] <= DES_IP[23:16];
  53. 207         arp_data[26] <= DES_IP[15:8];
  54. 208         arp_data[27] <= DES_IP[7:0];
复制代码

以上代码在复位时对数组进行初始化。
  1. 216             st_idle : begin
  2. 217                 if(pos_tx_en) begin
  3. 218                     skip_en <= 1'b1;  
  4. 219                     //如果目标MAC地址和IP地址已经更新,则发送正确的地址
  5. 220                     if((des_mac != 48'b0) || (des_ip != 32'd0)) begin
  6. 221                         eth_head[0] <= des_mac[47:40];
  7. 222                         eth_head[1] <= des_mac[39:32];
  8. 223                         eth_head[2] <= des_mac[31:24];
  9. 224                         eth_head[3] <= des_mac[23:16];
  10. 225                         eth_head[4] <= des_mac[15:8];
  11. 226                         eth_head[5] <= des_mac[7:0];  
  12. 227                         arp_data[18] <= des_mac[47:40];
  13. 228                         arp_data[19] <= des_mac[39:32];
  14. 229                         arp_data[20] <= des_mac[31:24];
  15. 230                         arp_data[21] <= des_mac[23:16];
  16. 231                         arp_data[22] <= des_mac[15:8];
  17. 232                         arp_data[23] <= des_mac[7:0];  
  18. 233                         arp_data[24] <= des_ip[31:24];
  19. 234                         arp_data[25] <= des_ip[23:16];
  20. 235                         arp_data[26] <= des_ip[15:8];
  21. 236                         arp_data[27] <= des_ip[7:0];
  22. 237                     end
  23. 238                     if(arp_tx_type == 1'b0)
  24. 239                         arp_data[7] <= 8'h01;            //ARP请求
  25. 240                     else
  26. 241                         arp_data[7] <= 8'h02;            //ARP应答
  27. 242                 end   
  28. 243             end                                                
复制代码

                  
在程序的第220行至241行代码,根据输入的发送类型、目的MAC地址和IP地址,重新更新数组里的值。            
  1. 265             st_arp_data : begin                          //发送ARP数据                           
  2. 266                 crc_en <= 1'b1;                                                              
  3. 267                 gmii_tx_en <= 1'b1;                                                         
  4. 268                 //至少发送46个字节                                                                  
  5. 269                 if (cnt == MIN_DATA_NUM - 1'b1) begin                                       
  6. 270                     skip_en <= 1'b1;                                                         
  7. 271                     cnt <= 1'b0;                                                            
  8. 272                     data_cnt <= 1'b0;                                                        
  9. 273                 end                                                                          
  10. 274                 else                                                                        
  11. 275                     cnt <= cnt + 1'b1;                                                      
  12. 276                 if(data_cnt <= 6'd27) begin                                                   
  13. 277                     data_cnt <= data_cnt + 1'b1;                                             
  14. 278                     gmii_txd <= arp_data[data_cnt];                                          
  15. 279                 end                                                                          
  16. 280                 else                                                                        
  17. 281                     gmii_txd <= 8'd0;                    //Padding,填充0                       
  18. 282             end              
复制代码

                                                               
程序第265行至第282行代码为发送ARP数据的状态。我们前面讲过以太网帧格式的数据部分最少是46个字节,ARP数据只有28个字节,因此在发送完ARP数据之后补充发送18个字节,填充的数据为0。
  1. 283             st_crc      : begin                          //发送CRC校验值                          
  2. 284                 gmii_tx_en <= 1'b1;                                                         
  3. 285                 cnt <= cnt + 1'b1;                                                           
  4. 286                 if(cnt == 6'd0)                                                              
  5. 287                     gmii_txd <= {~crc_next[0], ~crc_next[1], ~crc_next[2],~crc_next[3],      
  6. 288                                  ~crc_next[4], ~crc_next[5], ~crc_next[6],~crc_next[7]};     
  7. 289                 else if(cnt == 6'd1)                                                         
  8. 290                     gmii_txd <= {~crc_data[16], ~crc_data[17], ~crc_data[18],               
  9. 291                                  ~crc_data[19], ~crc_data[20], ~crc_data[21],               
  10. 292                                  ~crc_data[22],~crc_data[23]};                              
  11. 293                 else if(cnt == 6'd2) begin                                                   
  12. 294                     gmii_txd <= {~crc_data[8], ~crc_data[9], ~crc_data[10],                  
  13. 295                                  ~crc_data[11],~crc_data[12], ~crc_data[13],                 
  14. 296                                  ~crc_data[14],~crc_data[15]};                              
  15. 297                 end                                                                          
  16. 298                 else if(cnt == 6'd3) begin                                                   
  17. 299                     gmii_txd <= {~crc_data[0], ~crc_data[1], ~crc_data[2],~crc_data[3],      
  18. 300                                  ~crc_data[4], ~crc_data[5], ~crc_data[6],~crc_data[7]};     
  19. 301                     tx_done_t <= 1'b1;                                                      
  20. 302                     skip_en <= 1'b1;                                                         
  21. 303                     cnt <= 1'b0;                                                            
  22. 304                 end                                                                                                                                             
  23. 305             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),表示发送完成。
阿莫论坛发帖领航者专用247520.png

图 25.4.9 ARP发送采集的ILA波形图

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,设置界面如下图所示:
阿莫论坛发帖领航者专用247971.png

图 25.4.10 生成CRC代码设置界面

下载之后对比我们提供的源代码会发现,只需稍作修改就可以直接使用。
ARP控制模块的代码如下:
  1. 1  module arp_ctrl(
  2. 2      input                clk        , //输入时钟   
  3. 3      input                rst_n      , //复位信号,低电平有效
  4. 4      
  5. 5      input                touch_key  , //触摸按键,用于触发开发板发出ARP请求
  6. 6      input                arp_rx_done, //ARP接收完成信号
  7. 7      input                arp_rx_type, //ARP接收类型 0:请求  1:应答
  8. 8      output  reg          arp_tx_en  , //ARP发送使能信号
  9. 9      output  reg          arp_tx_type  //ARP发送类型 0:请求  1:应答
  10. 10     );
  11. 11
  12. 12 //reg define
  13. 13 reg         touch_key_d0;
  14. 14 reg         touch_key_d1;
  15. 15
  16. 16 //wire define
  17. 17 wire        pos_touch_key;  //touch_key信号上升沿
  18. 18
  19. 19 //*****************************************************
  20. 20 //**                    main code
  21. 21 //*****************************************************
  22. 22
  23. 23 assign pos_touch_key = ~touch_key_d1 & touch_key_d0;
  24. 24
  25. 25 //对arp_tx_en信号延时打拍两次,用于采touch_key的上升沿
  26. 26 always @(posedge clk or negedge rst_n) begin
  27. 27     if(!rst_n) begin
  28. 28         touch_key_d0 <= 1'b0;
  29. 29         touch_key_d1 <= 1'b0;
  30. 30     end
  31. 31     else begin
  32. 32         touch_key_d0 <= touch_key;
  33. 33         touch_key_d1 <= touch_key_d0;
  34. 34     end
  35. 35 end
  36. 36
  37. 37 //为arp_tx_en和arp_tx_type赋值
  38. 38 always @(posedge clk or negedge rst_n) begin
  39. 39     if(!rst_n) begin
  40. 40         arp_tx_en <= 1'b0;
  41. 41         arp_tx_type <= 1'b0;
  42. 42     end
  43. 43     else begin
  44. 44         if(pos_touch_key == 1'b1) begin  //检测到输入触摸按键上升沿
  45. 45             arp_tx_en <= 1'b1;           
  46. 46             arp_tx_type <= 1'b0;
  47. 47         end
  48. 48         //接收到ARP请求,开始控制ARP发送模块应答
  49. 49         else if((arp_rx_done == 1'b1) && (arp_rx_type == 1'b0)) begin
  50. 50             arp_tx_en <= 1'b1;
  51. 51             arp_tx_type <= 1'b1;
  52. 52         end
  53. 53         else
  54. 54             arp_tx_en <= 1'b0;
  55. 55     end
  56. 56 end
  57. 57
  58. 58 endmodule
复制代码

ARP控制模块的代码较简单,首先检测输入触摸按键的上升沿,当检测到上升沿之后,触发ARP顶层模块发起ARP请求信号;同时检测输入的arp_rx_done和arp_rx_type信号,当接收上位机的ARP请求信号后,触发ARP顶层模块发送ARP应答信号,将开发板的MAC地址发送给上位机。
25.5下载验证
将下载器一端连接电脑,另一端与开发板上的JTAG下载口连接,将网线一端连接开发板的PL网口(GE_PL),另一端连接电脑的网口,接下来连接电源线,并打开开发板的电源开关。GE_PL网口的位置如下图所示。
阿莫论坛发帖领航者专用250079.png

图 25.5.1 GE_PL网口位置

点击Vivado左侧“Flow Navigator”窗口最下面的“Open Hardware Manager”,此时Vivado软件识别到下载器,点击“Hardware”窗口中“Program Device”下载程序,在弹出的界面中选择“Program”下载程序。
程序下载完成后,PHY芯片会和电脑网卡进行通信(自协商),如果程序下载正确并且硬件连接无误的话,我们点击电脑右下角的网络图标,会看到本地连接刚开始显示的是正在识别,一段时间之后显示未识别的网络,打开方式如下图所示(WIN7和WIN10操作可能存在差异,但基本相同)。
阿莫论坛发帖领航者专用250410.png

图 25.5.2 点击网络图标

点击图 24.5.3中的“未识别的网络(无Internet)”,弹出如下图所示界面。
阿莫论坛发帖领航者专用250538.png

图 25.5.3 网络设置界面

点击“更改适配器”选项,弹出如下图所示界面。
阿莫论坛发帖领航者专用250622.png

图 25.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。鼠标右击图 24.5.5中的以太网,如下图所示:
阿莫论坛发帖领航者专用250923.png

图 25.5.5 鼠标右击“以太网”

点击“属性”,弹出如下图所示界面。
阿莫论坛发帖领航者专用251005.png

图 25.5.6 以太网“属性”界面

鼠标双击“Internet协议版本4(TCP/IPv4)”,弹出如下图所示界面。
阿莫论坛发帖领航者专用251110.png

图 25.5.7 设置以太网IP地址

在“Internet协议版本4(TCP/IPv4)”属性界面中,选择使用下面的IP地址,IP地址设置成192.168.1.102,并点击确定完成设置。
接下来以管理员身份打开电脑的命令的DOS命令窗口(注意必须以管理员身份打开),打开方式如下:
阿莫论坛发帖领航者专用251297.png

图 25.5.8 打开电脑DOS命令窗口

打开DOS命令窗口后,在命令行中输入“arp -a”,如下图所示:
阿莫论坛发帖领航者专用251397.png

图 25.5.9 输入命令“arp -a”

输入完成后,按下键盘的回车键,此时会弹出电脑中所有网络接口的ARP缓存表,我们只需要关注以太网接口的ARP缓存表(IP地址为192.168.1.102),如下图所示:
阿莫论坛发帖领航者专用251548.png

图 25.5.10 以太网接口ARP缓存表

可以发现,此时ARP缓存表中还没有开发板的MAC地址和IP地址,此时我们按下开发板的触摸按键(TPAD)。按下后,开发板会向电脑发起ARP请求,并且电脑会返回自己的MAC地址到开发板。
需要说明的是,在开发板发起ARP请求时,会将开发板的MAC地址和IP地址都发给电脑,此时电脑就已经获取到了开发板的MAC地址和IP地址,并更新至ARP的缓存表中,我们重新在DOS命令中输入“arp -a”,如图 25.5.11和图 25.5.12所示。
阿莫论坛发帖领航者专用251883.png

图 25.5.11 输入“arp -a”

阿莫论坛发帖领航者专用251949.png

图 25.5.12 开发板的MAC地址更新至缓存表中

由上图可知,此时以太网接口的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”,并按下按键的回车键,如下图所示:
阿莫论坛发帖领航者专用252325.png

图 25.5.13 输入“arp -d”

接下来重新在DOS命令中输入“arp -a”,来验证是否删除成功(如果没有以管理员运行会导致删除失败),输入完成后,如下图所示;
阿莫论坛发帖领航者专用252456.png

图 25.5.14 删除开发板MAC地址

此时我们之前获取的开发板MAC地址已经删除成功,接下来在DOS命令中输入“ping 192.168.1.10”,来让电脑发起ARP请求,如下图所示。
阿莫论坛发帖领航者专用252597.png

图 25.5.15 电脑发起“ARP”请求

需要说明的是,ping是一个十分强大的TCP/IP工具,它可以用来检测网络的连通情况和分析网络速度。ping命令是一个固定格式的ICMP(Internet控制报文协议)请求数据包,之后会发起ARP请求命令,所以我们这里是通过ping命令来间接发起ARP请求。由于开发板并没有实现ICMP协议,因此在ping时会请求超时,但是在ping的过程中发起的ARP请求,开发板会响应并返回ARP应答数据。
接下来再次在DOS命令中输入“arp -a”,查询是否成功获取到开发板MAC地址,如下图所示:
阿莫论坛发帖领航者专用252910.png

图 25.5.16 开发板MAC地址获取成功

由上图可知,电脑正确获取到开发板的MAC地址,并更新至ARP缓存表中。到这里,开发板实现的ARP协议就已经全部验证成功了。
接下来介绍一个以太网通信时经常使用的抓包软件,该软件位于开发板所随附的资料“6_软件资料/1_软件/Wireshark”目录下,也可以直接在网上搜索下载,我们现在打开Wireshark,界面如下图所示:
阿莫论坛发帖领航者专用253142.png

图 25.5.17 wireshark打开界面

双击上图所示的以太网或者先选中以太网,再点击上方红框选中的蓝色按钮,即可开始抓取本地连接的数据包,抓取界面如下图所示:
阿莫论坛发帖领航者专用253271.png

图 25.5.18 wireshark以太网打开界面

从上图可以看到,已经抓取到其它应用程序使用以太网发送的数据包,但是这些数据包并不是开发板发送的数据包,我们这个时候按下开发板的触摸按键(TPAD),就可以在wireshark中抓取到数据包了,抓取到的数据包如下图所示:
阿莫论坛发帖领航者专用253453.png

图 25.5.19 wireshark抓取到的数据包

上图中第38行数据包是开发板发送给电脑的ARP请求包,第39行数据包是电脑发送给开发板的ARP应答包,此时双击第38行即可看到开发板发送的详细数据,如下图所示:
阿莫论坛发帖领航者专用253606.png

图 25.5.20 wireshark抓取到的详细数据

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

阿莫论坛20周年了!感谢大家的支持与爱护!!

一只鸟敢站在脆弱的枝条上歇脚,它依仗的不是枝条不会断,而是自己有翅膀,会飞。
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-3-29 01:23

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表