做ZYNQ开发这些年,以太网这块我大部分时间都是走PS端的GEM控制器,Linux里一个socket下去,UDP包就出去了。但这次项目不一样,数据源是PL端的高速ADC,甲方要求从采样到UDP包上线的延迟控制在微秒级,而且要能完全自定义包内容。软件协议栈在这种需求下很尴尬——中断调度不确定、包格式不好定制、时序更没法精确控制。我索性把以太网MAC和UDP协议栈全放到PL里实现,PHY用RTL8211,跑通千兆和百兆自适应。整个过程踩了不少坑,尤其是在RGMII时序和自适应切换上。这篇文章把我的完整实现思路、关键代码、实测数据和踩坑记录都整理出来,给正在或准备在ZYNQ PL端做以太网开发的朋友做个参考。
1. 为什么绕开PS端GEM,非要在PL里做UDP
1.1 PS端GEM方案的痛点
ZYNQ的PS端自带Gigabit Ethernet MAC控制器,芯片内部已经集成了DMA、描述符管理、MDIO控制器,配合Linux的macb或gem驱动,上电就能跑千兆网。很多项目用这套方案,稳定且开发速度快。我之前的几个项目也都是这么干的,但这次的需求让软件方案走不下去。
第一是延迟。Linux协议栈从应用层write到数据真正出现在RGMII引脚上,中间要经过系统调用、套接字层、IP协议栈、驱动、DMA描述符,最后才到MAC。这个路径的延迟是毫秒级或至少几十微秒级别,而且不稳定,中断优先级、CPU负载、内核调度都会抖动。ADC数据要求微秒级出包,这个路径完全无法接受。
第二是包格式。软件协议栈只能按标准的IP/UDP报文格式发,如果我想在以太网帧里塞自定义字段、自定义前导,或者在包间隔里插入硬件时间戳,软件方案基本没戏。硬件方案在这些方面自由得多。
第三是PS端资源的消耗。ZYNQ的PS端跑Linux时,以太网中断、DMA操作都会消耗CPU资源。如果PS端还要跑控制算法、人机交互、数据记录,网络高负载时会挤占CPU时间。把网络处理下沉到PL,PS就彻底解放了。
1.2 三种方案对比
做项目之前我把可选方案列了个对比表:
| 方案 | 延迟 | 吞吐能力 | 包格式自由度 | 开发复杂度 | 资源占用 |
|---|---|---|---|---|---|
| PS + Linux socket | 几十微秒到毫秒级,不稳定 | 可达千兆线速,依赖驱动 | 低,只能标准报文 | 低 | 高,占用CPU |
| MicroBlaze + LwIP | 毫秒级,依赖软核中断 | 百兆量级,千兆很吃力 | 中,可部分修改 | 中 | 中 |
| 纯PL逻辑MAC+UDP | 微秒级,完全确定 | 可满千兆线速 | 高,任意自定义 | 高 | 低,不占CPU |
最终选择纯PL方案。PL里实现以太网MAC和UDP协议栈,本质是构建一条纯硬件的数据通路:应用数据写入FIFO,UDP发送引擎从FIFO读出数据,封装成帧,过CRC32校验,再经过RGMII接口送到RTL8211 PHY,全部是组合逻辑加状态机的流水线,没有软件参与。延迟可以做到几十个时钟周期。
1.3 项目对带宽的真实需求
这个项目的ADC是16bit、双通道、125MSPS,理论数据率是500MB/s,也就是4Gbps。显然单路千兆以太网扛不住原始码率,所以前端做了压缩和数据抽取,最终有效数据流约700Mbps左右。这个带宽值刚好卡在百兆和千兆之间,所以PHY必须能跑到千兆,同时为了兼容现场只有百兆网络的场景,又要能自动降到百兆工作。这也是RTL8211这种千兆PHY最常见的应用场景——自适应能力必须可靠,不能出现插上百兆交换机就断链的问题。
2. 硬件搭台:RTL8211电路要点与MDIO调试
2.1 RGMII接口信号连接
RTL8211和ZYNQ PL端通过RGMII接口连接,信号组如下:
- 发送时钟GTX_CLK:由MAC(PL)提供,千兆125MHz,百兆25MHz
- 发送数据TXD[3:0]:4bit DDR发送,时钟上下沿各送4bit
- 发送控制TXD_CTL:包含TX_EN和TX_ER,同样是DDR
- 接收时钟RX_CLK:由PHY恢复,从接收数据中提取的时钟
- 接收数据RXD[3:0]:4bit DDR接收
- 接收控制RXD_CTL:包含RX_DV和RX_ER
- MDIO / MDC:管理接口,PHY寄存器配置用
RTL8211的模式配置是通过strap引脚在复位时采样的。我这里把PHY固定在RGMII模式,PHYAD地址设成0x01。需要注意,PHYAD引脚同时影响MDIO的访问地址,如果设成多个值,MDIO命令里的PHY地址必须对应。
2.2 MDIO读不到PHY ID的问题
第一次上电调试,MDIO读寄存器2和寄存器3,返回的全是0xFFFF。用示波器看MDC和MDIO,波形是有的,方向也没错,但PHY就是不回答。
后来查手册才发现问题:RTL8211的上电时序里有个要求,必须先给复位引脚一个低电平脉冲,且低电平时间至少要10ms,拉高之后还要等至少50ms,内部时钟和寄存器才能稳定。我的板子复位信号是CPU GPIO控制的,FPGA加载完驱动GPIO复位,但MDIO状态机在FPGA配置完瞬间就开始轮询,根本没给PHY留够初始化时间。
解决办法是MDIO状态机加一个上电延时计数,FPGA一下子配置完成后先等100ms再启动MDIO访问。这个延时用简单的计数器实现就行,没必要用定时器。加上延时之后,PHY ID能稳定读回来,RTL8211E系列常见的ID是0x001CC9xx开头,具体型号略有差异,以手册为准。
2.3 RTL8211关键寄存器配置
RTL8211常用寄存器如下:
| 寄存器 | 名称 | 关键位 | 作用 |
|---|---|---|---|
| 0x00 | BMCR | bit15 复位,bit12 自适应使能,bit13/bit6 速率选择 | 基本控制 |
| 0x01 | BMSR | bit2 链路状态,bit5 自适应完成 | 基本状态 |
| 0x04 | 千兆控制 | bit8/bit9 千兆半/全双工广播 | 千兆能力广告 |
| 0x1A | PHYSR1 | bit15 链路,bit14 双工,bit13:12 速率 | 状态监视 |
| 0x1F | RTL8211扩展配置 | 可能影响RX时钟延迟等 | 扩展 |
实际配置流程是:
- 写寄存器0 = 0x1200,开启自适应并重启协商
- 写寄存器4 = 0x0140,广