FPGA驱动ZYNQ 7020实现UDP通信:从RGMII到AXI DMA链路搭建
2026/9/16 13:45:19 网站建设 项目流程

简介:ZYNQ 7020以太网UDP通信资源包面向FPGA开发者和嵌入式网络工程师,聚焦ZYNQ SoC上UDP通信的FPGA驱动与软硬件协同设计,系统梳理了从以太网帧收发包、UDP数据报封装解析到ARM侧协议栈处理的完整链路。压缩包共610个文件、约49.5MB,涵盖Verilog/VHDL逻辑源码、XDC管脚约束、DCP综合网表、Tcl/Shell/DO仿真脚本、ARM侧C驱动、示例应用及配置文档;其中HDL文件负责UDP引擎与MAC接口逻辑,XDC定义时序约束,脚本用于工程编译和仿真,C驱动处理中断与DMA,TXT/RST文档说明协议原理,目录按IP核、仿真、约束和驱动分层组织。已有996人学习下载。资源对UDP校验和计算、GMII/RGMII接口适配、DMA环形缓冲管理、中断触发以及ARP/IP/UDP协议栈协作等关键技术均提供了可综合的HDL实现和C驱动代码,并带有比特流文件与一键编译脚本,可结合Vivado工程直接运行,直观验证ZYNQ平台上的高速以太网通信,也可作为二次开发的底层驱动参考。

1. 用FPGA驱动ZYNQ 7020的UDP:先搞清楚哪条链路才叫驱动

在Zynq 7020上进UDP通信,新手最容易掉进坑里:配好Linux IP、socket一开、数据发出去了,就以为UDP通了。其实这条路全部在PS自带的千兆以太网MAC(GEM)和Linux协议栈里走,PL侧的FPGA逻辑根本没参与。标题里说的“FPGA驱动”,是指由PL侧的MAC接PHY芯片,把以太帧通过AXI DMA搬到DDR,再由设备驱动交给系统——网络接口从FPGA里长出来,CPU只在控制面干预。

这个方案解决两类典型问题:PS网口被业务占满时开第二路UDP;或者FPGA内部信号直接封装成UDP帧,绕开CPU逐层拷贝。高速数据采集、FPGA把ADC数据打包上传的场景尤为常用。适合的读者是调过RGMII时序、能看懂DMA描述符的嵌入式工程师,以及准备让自定义总线上送以太网的算法工程师。

2. 从RGMII到AXI Stream:搭建ZYNQ 7020的UDP物理链路

2.1 为什么选RGMII:引脚、电平和PHY选型

如果从头在PL侧开千兆以太网,第一选择就是RGMII。原因很朴素:GMII接口的TX/RX各8位数据线,加上时钟、控制、MDIO,整体超过30根信号,Zynq 7020的PL Bank引脚本来就不富裕,还要给DDR、LVDS图像接口留位置。RGMII把收发各压到4根数据线,时钟和数据都走DDR双沿,12根引脚就能完成一个千兆口。

SGMII更省引脚,但需要走PL的SerDes通道。7020上MGT Bank常常被PCIe、SATA或其它高速串口占用,为一个辅助网口让出SerDes资源并不划算。下表是三种PL侧千兆接口的常规对比:

接口数据线宽度时钟方式引脚消耗7020 PL侧使用场景
GMII8位并行125MHz SDR约30根较少,Bank资源宽裕且PCB允许大量走线时
RGMII4位DDR125MHz DDR约12根AXI Ethernet IP常规选项,第二网口首选
SGMII串行1.25Gbps SerDes4根MGT Bank空闲时可用,但常被PCIe抢走

PHY选型跟着接口走:88E1512、88E1518、RTL8211系列都支持RGMII到铜缆的转换。选型重点有三个:RGMII电平是否匹配PL Bank的VCCO,Zynq 7020的HR Bank常用2.5V供电;MDIO地址引脚是否有硬件拉拔,关系到设备树里PHY地址是0x0还是0x10;以及PHY的125MHz时钟输出是否要反送回FPGA当作GTX参考时钟,这会直接改变XDC里时钟约束的写法。

上电后第一步用MDIO读PHY的标识寄存器(寄存器2和3),确认MDIO连接和PHY地址正确,再进时序调试。这一步能省下好几个小时的“网口死活不link”排查时间。

2.2 RGMII时序约束与RXC相移

用Vivado的AXI Ethernet IP时,RGMII引脚约束看着简单,把端口名对上就行,真正影响收包率的是时钟与数据的相位关系。RGMII 2.0规范中,PHY送出的RXC与RXD之间存在最大1纳秒左右的Skew,到MAC侧时数据沿已经不在时钟采样窗口中心。常见做法是让FPGA内部对RXC经过IDELAY后再进IDDR采样,同时用XDC里的set_input_delay约束数据相对时钟的窗口:

set_property -dict {PACKAGE_PIN L16 IOSTANDARD LVCMOS25} [get_ports rgmii_rxd[0]] create_clock -period 8.000 -name rgmii_rxc_i [get_ports rgmii_rxc] set_input_delay -clock rgmii_rxc_i -max 1.000 [get_ports "rgmii_rxd[*]"]

逻辑说明:第一行把引脚绑定到具体封装位置并声明IO电平;第二行创建125MHz输入时钟对象,8ns周期对应千兆DDR;第三行声明RXD相对RXC的最大到达延迟为1ns。实际调试中,大多数PHY要求RXD比RXC晚0.5到1ns到达,但PCB走线长度会改变这个窗口,因此XDC里的具体数值要以PHY数据手册和实际误包率为准。

调这个延时时,最有效的验证方式是发固定长度帧,同时观察MAC侧CRC错误计数。需要注意,CRC错误计数上涨不一定表现为丢包,而是表现为接收帧被MAC丢弃,从用户侧看就是“偶尔少几个UDP包”。PCB布局把同组RGMII信号等长控制在50mil以内,能显著降低这类调试成本。

2.3 AXI Stream与以太网帧格式的映射

AXI Ethernet接收侧的AXI Stream输出是完整以太网帧:前导码和SFD已被MAC剥离,数据从目的MAC地址开始,到TLAST拉高表示帧结束。CRC在默认配置下也被MAC移除,不会出现在Stream里。搞清这一点很重要,很多从裸PL逻辑接手的人会在DMA缓冲区里看到帧尾多出4字节,那不是错误,而是TX_INCLUDE_FCS配置被打开了。

以太网帧格式对UDP解析的影响体现在三个固定偏移:帧头第12、13字节是EtherType,0x0800代表IPv4,0x0806代表ARP;IPv4头从偏移14开始,标准长度20字节;UDP头在IP头之后,源端口与目的端口的偏移由此计算。FPGA驱动层面的数据通路,本质上就是把RGMII上串行到达的字节按这个布局写入AXI Stream,再由DMA搬到内存。如果你在调试中看到PC端能抓到板子发出的ARP,却收不到UDP数据,回头查这一层的EtherType和IP头长度字段,往往比抓协议栈更有价值。

3. 在Vivado里搭建AXI Ethernet加AXI DMA的UDP收发链路

3.1 带PS还是纯PL:先定数据处理归属

做UDP通信前需要先回答一个问题:要不要把协议栈放进FPGA逻辑。纯PL方案的思路是在PL里用RTL实现MAC、ARP、IP、UDP校验和,所有包处理都在硬件里完成;带PS方案则让PL只做MAC和数据搬运,IP/UDP协议栈交给ARM上的Linux。一线工程里带PS方式更常见,因为Zynq的CPU本来就有富余算力,Linux协议栈的稳定性远超自研RTL网络栈,而且调试工具丰富。纯PL适合极低延迟或要求零CPU介入的数据采集前端,但ARP、ICMP、分片重组都要自己写,调试周期往往按周计算。

从“FPGA驱动”这个标题看,两条路都说得通。下面以带PS方案为主线展开,因为AXI Ethernet、AXI DMA、设备树、Linux驱动这一整套组合是Zynq平台最成熟可靠的UDP落地方案,踩坑最少。

3.2 块设计最小组合:IP选型与关键配置

在Vivado Block Design里,一个最小可跑通UDP的工程包含以下IP:

  • AXI Ethernet:选择1000Mbps、RGMII、使能MDIO
  • AXI DMA:S2MM和MM2S两个通道都打开,用于收发两个方向
  • AXI Interconnect:把DMA的存储映射接口连到PS的HP口
  • Processing System 7:启用HP0、GP0、PL中断
  • AXI BRAM Controller:可选,只在不用DDR做DMA缓冲时使用

创建AXI Ethernet并设置参数的TCL脚本示例如下:

create_bd_cell -type ip -vlnv xilinx.com:ip:axi_ethernet:7.2 axi_eth_0 set_property -dict [list \ CONFIG.PHY_TYPE {RGMII} \ CONFIG.RX_INCLUDE_FCS {false} \ CONFIG.TX_INCLUDE_FCS {false} \ CONFIG.ENABLE_MDIO {true}] [get_bd_cells axi_eth_0]

参数说明:PHY_TYPE选RGMII,对应外部PHY芯片接口;RX_INCLUDE_FCS和TX_INCLUDE_FCS都设为false,让MAC自动剥离和追加CRC,避免DMA缓冲区里出现多余字节;ENABLE_MDIO开启管理通道,驱动才能通过MDIO读取PHY状态和协商结果。

连线层面,AXI Ethernet的s_axi配置口接GP口或HP口均可,中断接PS的PL中断;AXI DMA的S2MM从以太网接收AXI Stream,MM2S向以太网发送流;DMA的m_axi接AXI Interconnect后挂到HP0。有一个容易忽略的点是DMA中断和Ethernet中断要在Block Design里同时引出,Linux驱动通常同时依赖这两个中断源,设备树里也要对应配两个中断号。

3.3 DMA描述符与缓冲区对齐的硬约束

AXI DMA在PS侧看来就是一组描述符加数据缓冲区。描述符由驱动维护,数据缓冲区要求物理地址连续,且基地址和长度都满足对齐要求。在裸机裸金属程序中,Xil_DCacheFlush()必须在CPU写完数据后调用,否则DMA读到的可能是Cache里的旧数据;Linux下用dma_alloc_coherent()分配缓冲区,才能保证设备地址和CPU虚拟地址在同一个映射视图里。

UDP包进入DMA缓冲后,描述符的环回顺序决定接收连续性。如果环缓冲区只配置一个描述符,上一帧还没被用户程序取走,下一帧就会覆盖同一块内存,表现为“UDP数据时而新时而旧”。常规设计至少配置4个独立描述符,并在驱动里维护一个“生产者-消费者”索引。调试时如果发现高流量下丢帧,先看描述符数量而不是先去调RGMII延时,这往往是缓冲区深度不足造成的。

4. Linux侧驱动与UDP应用:让FPGA网卡出现在eth1

4.1 设备树与驱动匹配

AXI Ethernet在Linux内核里有现成驱动,设备树要做的是把硬件资源告诉驱动。一个典型节点如下:

ethernet@a4010000 { compatible = "xlnx,axi-ethernet-1.00.a"; reg = <0x0 0xa4010000 0x0 0x40000>; interrupts = <0 57 4>; local-mac-address = [00 0a 35 00 01 00]; phy-handle = <&phy0>; mdio { #address-cells = <1>; #size-cells = <0>; phy0: ethernet-phy@0 { reg = <0>; }; }; };

字段说明:compatible决定内核用哪个驱动匹配;reg必须是Vivado地址向导分配给该IP的基地址,要和Block Design里的地址一致;interrupts里的57是PL中断经GIC映射后的SPI编号,具体值以Vivado向导打印为准;phy-handle指向MDIO子节点,而子节点中的reg填写PHY芯片的MDIO地址。最常踩的坑是PHY实际地址与reg不一致,驱动初始化时找不到PHY,直接导致link状态永远为down。

修改设备树后重新编译启动,ifconfig -a能看到新网卡接口,通常命名为eth1。如果设备树没生效,先检查/proc/device-tree里对应节点是否存在,再确认内核里AXI Ethernet驱动是否编译进内核。

4.2 用户态UDP应用的两条通道

网卡注册成功后,最直接的方式是把它当普通网卡用,绑定IP后通过socket收发UDP。这种方式的优点是完全复用Linux协议栈,代码和PC端没有区别。另一种方式是用户态直接读取DMA缓冲区,适合PL侧硬件已经完成了IP/UDP解析、只想拿净荷的场景,典型实现是用UIO框架:

int fd = open("/dev/uio0", O_RDWR); void *buf = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); uint16_t ethertype = (buf[12] << 8) | buf[13]; if (ethertype == 0x0800 && buf[23] == 17) { uint16_t dport = (buf[36] << 8) | buf[37]; printf("UDP dport=%u\n", dport); }

代码里的偏移量对应标准以太网帧:buf[12]和buf[13]是EtherType,0x0800表示IPv4;buf[23]是IP头中的协议字段,17表示UDP;buf[36]和buf[37]是UDP目的端口。这个片段用于验证PL侧DMA搬运过来的数据排列是否符合预期,特别是网络字节序与主机字节序的差异,调RGMII时也经常用它快速确认链路。

需要说明的是,UIO方式要求驱动把DMA缓冲区通过mmap暴露给用户态,并保证中断到达后用户态能感知。相比socket方式,UIO省去了两次数据拷贝,但把协议解析工作推给了应用。如果FPGA里有UDP卸载引擎,选UIO;如果PL只做MAC,直接用socket更省事。

4.3 没有ARP就没有UDP:排查还是要从协议栈看起

Linux协议栈发送IP包前会先发ARP请求,只有拿到目的IP对应的MAC地址,UDP数据帧才会真正发出。因此“网线插着、ifconfig正常、socket却没数据”时,第一步查ARP,而不是查UDP代码本身。PC端用Wireshark抓包,判断依据如下表:

现象问题所在
PC抓不到板子发出的ARP请求RGMII TX方向、PHY link、MAC使能状态
ARP能看到但没有回应DMA接收描述符、中断配置、设备树phy-handle
ARP通了但UDP收发失败应用层端口绑定、防火墙、网络调试助手设置

第三条在Linux里很常见:绑定端口时不指定IP或错误指定IP,UDP包发到了别的接口上;或者PC防火墙拦截了来自FPGA网卡的包。把防火墙关掉再测,通常能立刻区分是驱动问题还是应用问题。

5. 用MDIO环回和Wireshark确认FPGA侧包处理正确

5.1 MDIO数字环回是最快的问题分割手段

UDP发不出去时,第一步不是换网线,而是让PHY进入数字环回模式。在这种模式下,PHY在芯片内部把发送端的RGMII数据直接送回接收端,不经过网线物理链路。FPGA发出一个UDP包,如果接收方向能看到同样的帧,就说明FPGA侧的TX/RX数据通路、AXI DMA和驱动逻辑都成立,问题被隔离到PHY与线缆侧;反之,环回不通则说明问题在FPGA内部链路。

88E1518等PHY的数字环回控制通常位于寄存器0的bit14,通过MDIO写入即可开启:

mdio-tool eth1 phy_write 0x0 0x14 0x4000

参数说明:第一条0x0是MDIO地址,第二条0x14是寄存器地址,0x4000对应bit14置1。不同PHY芯片寄存器定义不同,这里只是常用示例。开启环回后,要关闭自适应并固定1000Mbps全双工,否则PHY在环回条件下无法完成正常协商,现象反而更奇怪。

5.2 Wireshark对拍时看帧格式,不只是看UDP载荷

PC端抓包时,过滤条件直接锁定FPGA的MAC和UDP端口:

eth.addr==00:0a:35:00:01:00 && udp.port==5000

抓到的包要检查三处:EtherType是否为0x0800,确认IPv4帧被正确递交;IPv4头校验和是否错误,错误说明FPGA侧做了IP层处理但实现有缺陷;UDP校验和是否为0。不少自研PL协议栈为省逻辑把UDP校验和写成0,IPv4允许这个做法,但部分硬交换芯片会直接丢弃零校验和UDP帧。因此商用验证时,最好让PL把UDP校验和计算出来,别省这个逻辑。

5.3 收尾技巧:用两个计数定位丢包点

网络基本跑通后再做一轮压测,同时盯两个数值:FPGA内部AXI Stream的TLAST脉冲计数,以及PC端Wireshark收到的帧计数。两个数字应接近1:1。如果TLAST很多但Wireshark收得少,问题在RGMII发送侧或PHY,调IDELAY和发送时序;如果Wireshark收得多但板子侧面TLAST少,问题在接收方向。Zynq 7020的UDP链路每一层都有清晰的特征,顺着这个两计数法能比盲改代码更快收窄问题。下一回遇到UDP丢包,先跑一次PHY环回确认内部基线,再动RGMII延时,省下的是按天计算的调试时间。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询