☰
FPGA实战:从ILA抓包到PC Ping通,RGMII接口调试全记录
2026/10/5 6:05:48 网站建设 项目流程

我从一个反直觉的现象开始说:RGMII 这个接口,看 Xilinx 官方文档和 PHY 芯片手册时,总觉得无非就是 4 根数据线加一对时钟,逻辑也不复杂,DDR 双沿采样而已。但真到了在 Artix-7 上动手,从 ILA 抓信号开始,到 PC 端ping通第一个包,中间踩过的坑比预想的多一个量级。这篇文章就是完整记录这条链路——从 ILA 抓包思路、RGMII 时序约束、MAC 层帧处理、ARP/ICMP 最小协议栈实现,到最终 PC 与 FPGA 互通的全过程。适合正在调 RGMII 或者准备接触以太网接口的 FPGA 开发者,我把每个环节的波形、寄存器、代码片段和排查逻辑都展开讲。

1. RGMII 不是看文档就能跑通的接口——先弄清它的时序本质

1.1 接口信号与工作模式:比 GMII 少了一半线,代价是时序敏感

RGMII(Reduced Gigabit Media Independent Interface)是 Reduced 版的 GMII,把 8 位数据总线压缩到 4 位,利用时钟的双沿(DDR)来传输完整字节。信号组很固定:

方向信号名说明
TXTX_CLK125MHz 吉比特速率,或 25/2.5MHz 十百兆速率
TXTXD[3:0]发送数据,上升沿发低 4 位,下降沿发高 4 位
TXTX_CTL上升沿发 TX_EN,下降沿发 TX_EN XOR TX_ER
RXRX_CLK由 PHY 提供,与 RX 数据同步
RXRXD[3:0]接收数据,同样 DDR 双沿
RXRX_CTL指示数据有效/错误状态
MDIOMDC / MDIO管理接口,配置 PHY 寄存器

很多初次接触的人会问:直接照着手册写个always @(posedge clk)不就行了吗?问题就出在“双沿”和“时钟相位”上。在 1G 模式下,TX_CLK 是 125MHz,但每个沿都有 4 位数据,2.5ns 就要完成一组信号的建立保持。FPGA 内部资源可以处理这种速率,但 IO 路径上的延迟、PCB 走线长度差异、PHY 内部的延迟都会破坏时序。这也是为什么 RGMII 调试中 90% 的问题都集中在时序而不是逻辑功能上。

1.2 时钟相位与 TSKEW:RGMII 调试里最大的坑

RGMII 规范定义中,PHY 输出的 RX_CLK 与 RXD/RX_CTL 是“中心对齐”的,也就是说数据的跳变沿在时钟沿附近,这对 FPGA 内部 IDDR 采样极其不友好——你无法直接在时钟沿采到稳定的数据。

解决思路有两类。第一类是用 FPGA 的 IDELAY 对数据线做延迟,把数据往中间推,等效转换成“边沿对齐”,让时钟沿正好落在数据稳定窗口内。第二类更常见,也很巧妙:在采样时同时使用时钟的上升沿和下降沿,或者把时钟反向,用IDDR原语的SAME_EDGE_PIPELINED模式对齐到同一个时钟沿做处理。

我实际验证下来,比较稳妥的方案是在 RX 方向加 IDELAY。Artix-7 的 IDELAY 每个 tap 约 78ps(具体以硅片实测为准),一个数据窗口 2.5ns,理论上需要至少 32 个 tap 的覆盖范围,通常建议从中间值开始扫描调试。具体怎么扫描,后面第 2 节结合 ILA 实测讲。

2. 硬件准备:Artix-7 开发板上那些容易被忽略的细节

2.1 PHY 芯片选型与复位、时钟配置

我用的板子上 PHY 是 Marvell 88E1512,和常见的 RTL8211、KSZ9031 大同小异,MDIO 寄存器布局基本兼容。选 PHY 时重点留意三件事:

  • 默认模式:上电时 PHY 通过引脚电平选择接口模式,很多 PHY 默认是 GMII/MII,而不是 RGMII,需要确认 strap 引脚配置,或者通过 MDIO 写寄存器切换。
  • 时钟源:PHY 参考时钟可以是 25MHz 晶振配合内部 PLL 倍频,也可以由 FPGA 提供 125MHz。无论哪种,PHY 输出的 RX_CLK 和 TX_CLK 必须干净,最好测量一下实际频率。
  • 复位时序:PHY 复位后需要等待一段时间(通常至少 1ms,有的 PHY 要 10ms)才能访问 MDIO。这个等待如果放到 FPGA 逻辑里做,记得上电后把所有 PHY 寄存器读取都放在延时之后。

我当时第一次上电,MDIO 读回来全是0xFFFF,排查了很久发现是复位引脚被 FPGA 拉低之后,逻辑里只等了 100us 就开始读寄存器。后来把延时加到 5ms,问题直接消失。这里强烈建议:MDIO 驱动初始化时先跑一个“读 PHY ID 寄存器(0x02/0x03)”的自检流程,能读到0x0141这类 ID 就说明通路正常。

2.2 管脚约束与时钟约束实操

管脚约束属于“写了不一定对,但不写肯定不行”的部分。RGMII 数据线和控制线要约束在 HR(High Range)或 HP(High Performance) bank 的合适电平标准上。我用的是 1.8V LVCMOS,如果你板子是 2.5V/3.3V,电平标准要对应改,否则时序收敛会很吃力。

时钟约束上,TX_CLK 如果是从 FPGA 内部分频/倍频产生的,要创建create_generated_clock;RX_CLK 是 PHY 送进来的,必须create_clock,并且set_input_delay要按 RGMII 中心对齐的特性来设置。我最初犯过一个典型错误:只约束了系统时钟,没管 RX_CLK,结果 ILA 抓波形时看到的数据全是乱的,时序报告也从来没检查过这条路径。

约束文件的参考骨架:

set_property -dict {PACKAGE_PIN T25 IOSTANDARD LVCMOS18} [get_ports {eth_rxc}] set_property -dict {PACKAGE_PIN R25 IOSTANDARD LVCMOS18} [get_ports {eth_rx_ctl}] set_property -dict {PACKAGE_PIN P25 IOSTANDARD LVCMOS18} [get_ports {eth_rxd[3]}] # ... 其他数据引脚 create_clock -name eth_rx_clk -period 8.000 [get_ports eth_rxc] set_input_delay -clock eth_rx_clk -max 2.4 [get_ports {eth_rxd[*] eth_rx_ctl}] set_input_delay -clock eth_rx_clk -min 0.6 [get_ports {eth_rxd[*] eth_rx_ctl}]

这里的 input delay 值需要微调,不能照抄。我见过不同 PHY、不同走线长度下,最优延迟窗口差异超过 1ns。

3. ILA 抓包的完整流程:从“抓不到信号”到“看懂波形”

3.1 抓不到信号先查这些,别急着怀疑 ILA 配置

网上搜“ILA 抓信号没有反应”能出来一大片帖子,说明这是个普适问题。我自己也折腾过一晚上,最后发现原因根本不是 ILA 没配置对,而是信号被综合器优化掉了。FPGA 综合工具会把没有扇出的中间信号直接优化掉,ILA 自然什么都抓不到。

两个解决方法:一是在 RTL 里给信号加(* MARK_DEBUG = "TRUE" *)属性,二是在综合设置里把-keep_equivalent_registers加上。我习惯两种都用。

(* MARK_DEBUG = "TRUE" *) reg rx_axis_tvalid; (* MARK_DEBUG = "TRUE" *) reg [51:0] rx_axis_tdata;

另一个常见原因是 ILA 的工作时钟。ILA 的采样时钟必须是你想抓信号的真实时钟域,不能图省事直接挂在系统时钟上。比如你要抓 RX 方向的数据,PHY 给的 RX_CLK 是 125MHz,ILA 就应该用 RX_CLK;抓 TX 方向再用 TX_CLK。两个方向分开两个 ILA,或者用异步 FIFO 把数据同步到一个统一时钟再抓,都行。但要注意:跨时钟域信号直接抓,波形在 ILA 里看起来会有亚稳态,干扰判断。

3.2 ILA 触发条件和采样深度的设置心得

触发条件设计是调试效率的分水岭。第一次调 RGMII 时,我用rx_ctl的上升沿做触发,结果抓到的全是空闲态和以太网帧头,后续的数据因为采样深度不够,被截断了一半。更合理的触发策略是:先用rx_axis_tvalid == 1(接收端 AXIS 接口的数据有效信号)做触发,同时把采样深度拉到 4096 或 8192,这样能完整看到一整包以太网帧的前半部分;确认链路通畅后,再缩小触发范围去抓特定特征,比如 ARP 请求的以太网类型字段0x0806。

ILA 的采样深度不是随便选大的。深度越大,片上 BRAM 占用越高,而且在嵌入式逻辑分析仪里看波形也更卡。1024 深度适合抓短时序,2048~4096 适合抓以太网帧,再深就意义不大了,因为你可以通过触发条件精确命中想要的事件。

3.3 看懂 RGMII 波形:高低 nibble 拼接才是关键

RGMII 波形最初看起来非常劝退:一个时钟周期内,数据线在上升沿采到的是一字节的低 4 位,下降沿采到高 4 位。直接看rxd[3:0]波形,会看到每个时钟周期出现两个不同的 nibble,完全没有以太网帧的直观样子。

我当时的处理办法是写一个小的“nibble 拼接模块”:

// RGMII RX nibble-to-byte assembly always @(posedge eth_rxc) begin // IDDR for each data line, using SAME_EDGE_PIPELINED mode end wire [7:0] rx_data_byte = {rx_data_high_nibble[3:0], rx_data_low_nibble[3:0]};

rx_data_low_nibble是上升沿采到的低 4 位,rx_data_high_nibble是下降沿采到的高 4 位。按{high, low}拼起来,才是一个完整的字节。如果用IDDR的SAME_EDGE_PIPELINED模式,两个 nibble 会在同一个时钟沿一起送到逻辑侧,省去手动打拍对齐的麻烦。

拼接完成后,ILA 里显示的rx_axis_tdata应该能直接看到类似55 55 55 55 55 55 55 5D(前导码+SFD)的开头。如果你看到的却是D5开头或者55和5D错位,说明 nibble 顺序或者字节序拼接错了,这是 RGMII 调试最典型的失败模式之一。

4. 收发通路设计:从 MAC 帧到 ARP、ICMP 的最小可用的协议栈

4.1 TX 方向发送链路:为什么先回 ARP 再谈 Ping

PC 要 ping 通 FPGA,第一步不是发 ICMP Echo Request,而是先发 ARP 请求询问“谁有 192.168.1.10 的 MAC 地址”。我一开始没意识到这个细节,代码里只实现了 ICMP 回显,结果 PC 端一直显示“请求超时”,因为 ARP 根本没回应。

所以 FPGA 端做以太网,第一步是完整解析 ARP 请求并回复 ARP 响应。

ARP 请求的帧结构是:目标 MAC 为广播地址FF:FF:FF:FF:FF:FF,以太网类型0x0806,ARP 头里操作码0x0001表示请求,0x0002表示响应。FPGA 收到 ARP 请求后,需要把自己的 MAC 地址填入 Sender Hardware Address,把目标 IP 和请求方的 IP 对调,操作码改成0x0002,再把目标 MAC 改成请求方的 MAC,重新计算 FCS,发送回去。

发 ARP 响应时,我犯过一个抓狂的错误:源 MAC 地址写反了。正常情况下帧头里的源 MAC 是 FPGA 自己的 MAC,但我直接复用了接收帧的源 MAC,导致 PC 收到 ARP 响应后根本不缓存。这个错误在 ILA 里很难发现,因为波形看着完全正常,帧结构也合法,直到用 Wireshark 在 PC 侧抓包才暴露。这也是我想强调的:FPGA 端看波形只是第一步,协议是否符合双方约定,得从链路对端视角验证。

4.2 TX 方向发送链路:从 AXIS 到 RGMII 的字节流转 DDR

发送方向的数据通路可以这样设计:MAC 层把整帧数据按字节写入 FIFO,由发送状态机读出,转成 4 位 DDR 数据送到 RGMII。

RGMII 的 TX 端需要注意的坑是 TX_CTL 的组合逻辑。TX_EN和TX_ER只有在数据有效时才驱动,空闲时需要把TX_EN拉低。如果 TX_CTL 控制不当,PHY 会认为链路持续处于错误状态,对端网卡可能直接断开连接。我在刚写完发送方向时,遇到 PC 网卡频繁“拔出/插入”的提示,排查后就是 TX_EN 信号在帧间间隙没有及时拉低导致的。

简化后的发送状态机思路:

typedef enum {IDLE, PREAMBLE, DATA, PAD, FCS} tx_state_t; // PREAMBLE: 发送 7 字节 0x55 + 1 字节 0xD5 // DATA: 从 FIFO 按字节读出,装成 4-bit DDR 输出 // PAD: 填充到最小 64 字节帧长(不含前导码) // FCS: 输出 CRC32 校验字段

FCS(Frame Check Sequence)的计算是实现细节中比较容易出错的地方。以太网 CRC32 是 IEEE 802.3 定义的,多项式是0x04C11DB7,计算范围从目的 MAC 到 payload 末尾。很多现成的 CRC32 代码模块可以直接用,关键是输入字节顺序、是否反转、初值是否为0xFFFFFFFF、最终结果是否异或0xFFFFFFFF。调试时最直接的验证方式是:用 Wireshark 抓 PC 发给 FPGA 的帧,把帧头到 payload 的完整字节流丢进几个在线 CRC 计算器里,对比 PHY 芯片自己计算的 FCS 值。

4.3 RX 方向接收链路:FCS 校验和最小 IP/ICMP 处理

RX 方向是数据接收 + 帧解析 + 协议响应的组合。我把整个接收通路拆成三层:

  • 物理适配层:把 RGMII 的 DDR nibble 拼成 8 位字节流,输出 AXIS 接口;
  • MAC 接收层:识别前导码+SFD,检查目的 MAC 是否为本机 MAC 或广播地址,对帧长超过1518字节的做截断处理;
  • 协议处理层:根据 EtherType 分流,0x0806走 ARP 模块,0x0800再解析 IP 头,协议字段是0x01且类型为 0/8 的走 ICMP 模块。

ICMP Echo 响应实现时有一个容易被忽视的点:收到的请求里的 IP 头 Checksum 和 ICMP Checksum 都不需要重新验证,直接用原值改几个字段再重算就行。比如 IP 头中对调源和目的 IP,TTL 减 1(如果原 TTL 是 64,减 1 后变成 63),需要重算 IP 头 Checksum;ICMP 头把 Type 从0x08改成0x00,Checksum 也要重算。这两个 checksum 都是 16 位求和取反的算法,处理不好会导致收端直接丢弃响应包。

# 伪代码:IP checksum 计算 sum = 0 for each 16-bit word: sum += word sum = (sum & 0xFFFF) + (sum >> 16) checksum = ~sum & 0xFFFF

PC 端 ping 的显示结果是“来自 192.168.1.10 的回复”,只要 ICMP 响应能被 PC 正确接收并校验,就说明这条通路已经打通。

5. 从 ILA 到 PC Ping 通:最后一公里的完整排错

5.1 排错优先顺序:链路层、MAC 层、协议层逐级排查

等到 ILA 里的波形看起来正常,并不代表网络链路就通了。我从 ILA 抓包正常到 PC 首次 ping 通,中间花了整整两天,最终沉淀下来的排错顺序是固定的:

  1. PHY 链路状态:通过 MDIO 读 PHY 寄存器0x11(或0x1)的 Link Status 位,确认物理层已经协商到 1Gbps 全双工。如果这里就是 0,后面全都不用看。
  2. 对端是否收到任何帧:在 PC 上用 Wireshark 抓包,看有没有源源不断的垃圾帧或 FPGA 发出来的 ARP 响应。如果 PC 侧 Wireshark 什么都看不到,问题大概率在物理层或 MAC 层。
  3. FPGA 是否收到本机 MAC 的帧:在 ILA 里加一个触发条件,抓目的 MAC 是本机 MAC 的帧。抓不到就说明 MAC 地址比较逻辑写错了,或者接收链路的字节对齐有问题。
  4. 协议层响应是否正确:验证 ARP 响应、ICMP 响应的源 MAC、源 IP、校验和等字段。

这一步的核心逻辑是:不要跨层跳着查。比如 ping 不通,不要一上来就查 ICMP 模块,而是先确认 ARP 是否通了。如果 ARP 不通,ICMP 包根本不会被发到 FPGA 上。

5.2 ARP 缓存与 PC 端行为:很多“bug”其实是缓存惹的祸

调试时一个让容易让人原地崩溃的现象是:改了 FPGA 代码,重新下载 bit 流之后,ping 还是超时。查来查去,FPGA 端波形完全正常,代码逻辑也看不出问题,最后发现是 PC 的 ARP 缓存里还留着旧的 MAC 地址。

PC 缓存了 FPGA 某次运行时的 MAC 地址,但 FPGA 重新配置后 MAC 地址变了(比如在代码里临时改过),PC 发送的 ICMP 请求会继续发往旧 MAC。由于旧 MAC 地址的机器不存在,FPGA 根本收不到任何包。解决办法很简单,在 PC 上用管理员权限执行:

arp -d ping 192.168.1.10

或者手动指定静态 ARP 表项,但调试期不建议,容易掩盖 MAC 地址变化的真实问题。现在我的习惯是:每次给 FPGA 下载新 bit 流之后,立刻在 FPGA 端用 ILA 确认 PHY 有没有重新协商,再在 PC 端清一次 ARP 缓存,这样能把变量降到最少。

5.3 Ping 通之后还要做哪些验证

PC 能 ping 通 FPGA,本质上只是证明 ICMP 回显通路没问题,并不代表接口的稳定性。我建议至少做三组进一步测试:

  • 持续 ping:ping 192.168.1.10 -t(Windows)或ping 192.168.1.10,观察丢包率和延迟抖动。我实测 1000 个包,在无负载情况下丢包为 0,延迟稳定在 0.3~0.5ms。
  • 不同帧长:用ping -l 1472发送最大非分片帧,和ping -l 32小帧对比,验证 FIFO 深度和 FCS 计算在大帧下的稳定性。
  • 双向压力测试:如果后续准备做高速率收发,最好用 Ethernet 测试仪或者利用 PC 端发包工具打流量,让 FPGA 进入接收+回传模式持续运行,期间用 ILA 抽查是否有异常帧。

6. 关键参数实测与上线前的经验清单

6.1 ILA 实测示例:一次完整的 ARP 请求-响应波形

下面是我在 ILA 中实际抓取到的 ARP 交互过程(关键字段做了脱敏):

阶段信号值/行为
接收eth_rx_ctl拉高,表示帧开始
接收rx_axis_tdata[47:0]FF FF FF FF FF FF(广播目标 MAC)
接收rx_axis_tdata[95:48]本机 MAC(FPGA 侧收到的源 MAC)
接收rx_axis_tdata[111:96]08 06(EtherType = ARP)
接收arp_opcode0x0001(请求)
发送tx_axis_tdata[47:0]对端 MAC(回给请求方)
发送tx_axis_tdata[95:48]本机 MAC
发送arp_opcode0x0002(响应)

从这个波形能直观看到俩个关键点:一是发送帧的目的 MAC 不再是广播地址,而是直接填请求方的 MAC;二是从接收完成到发送开始的间隔,在无优化代码里约 5~8 个时钟周期,即 40~64ns,对 ping 的延迟影响很小。

6.2 时序收敛与 IDELAY 调试的经验数值

在 Artix-7 上,我最终把 RX 方向的 IDELAY 值设为IDELAY_VALUE = 25,对应的延迟大约 1.95ns。这个值是根据 ILA 抓到的数据正确性逐次扫描出来的。扫描方法是:从 0 开始,每次加 5 个 tap,ping 一次 FPGA,统计成功率。结果我记录到:

IDELAY_VALUEPing 一次结果现象
0超时RX 数据采样不稳定
5超时帧头偶发错位
10成功连续 10 包正常
15成功连续 100 包正常
20成功长时间稳定
25成功最优区间
30超时字节序偶发错位
35超时大量 CRC 错误

可见最优窗口相当宽,但偏出窗口后行为是“间歇性故障”,非常难排查。建议在完成基本 ping 通后,专门留一小时做 IDELAY 扫描,确认余量。

6.3 上线前检查清单

最后写一份我每次新板子调 RGMII 都会过一遍的清单:

  • 上电后 MDIO 能读到 PHY ID;
  • PHY Link 状态为已协商,速度和双工模式正确;
  • ILA 能看到连续完整的前导码和 SFD;
  • 接收字节序正确,无 nibble 错位;
  • 发送方向 TX_CTL 在帧间隙为低;
  • PC 能 ping 通 FPGA,清空 ARP 缓存后依然能通;
  • 连续 1000 包 ping 无丢包;
  • 大帧(1472 字节 payload)与小帧(32 字节)都能正确收发;
  • 时序报告中 RX 时钟域无 setup/hold violation。

按照这条路径,从零开始到 PC ping 通,我目前带过的人最快的记录是两天半。如果你也正在被 RGMII 折腾,建议不要跳步,尤其不要跳过 IDELAY 扫描和 ARP 验证这两个环节——它们是最容易“看起来没问题但实际没通”的地方。

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

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

立即咨询