ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

FPGA千兆以太网UDP通信实现:从RGMII时序到Wireshark抓包实战

FPGA千兆以太网UDP通信实现:从RGMII时序到Wireshark抓包实战 1. 为什么大家都卡在“千兆以太网”这一步FPGA 开发做到一定阶段你会发现十个项目里有七八个绕不开网络通信。无论是做高速 ADC 采样后的数据上传、图像采集与实时处理还是搭建一个自定义协议的数据通路最终都要靠以太网把数据从板卡上弄出去。千兆以太网这个坎早过晚过都得过。这个实验是我在 FPGA 学习路上投入时间最久、踩坑最多也收获最大的一块内容。它不像流水灯、串口收发那样只要对着时序图写几个状态机就能通它牵扯到物理层芯片配置、RGMII 接口时序、MAC 帧封装、IP/UDP 协议栈裁剪、CRC 校验、跨时钟域处理任何一个环节出问题表现出来都是同样的症状——抓包抓不到ping 不通速度上不去。而这恰恰是真实工程项目中排障难度最大的那一类问题。所以这篇内容主要面向两类人一是正在做 FPGA 入门到进阶过渡、被 RGMII 时序折腾得头疼的学习者二是想在板卡上快速跑通一个能用的千兆 UDP 通路、为图像/ADC 数据搬运打基础的开发者。我会按自己实际做过的完整流程从选型、原理、写码、仿真到上板抓包把每一步怎么想、怎么写、怎么查全部分享出来。2. 整体方案设计先想清楚你要“干掉”的是哪一层2.1 先问自己是“通”就行还是要“快”做千兆以太网之前我建议你先想明白一个问题你的目标是把一个 UDP 包从板卡发到 PC 上还是要把某路数据以线速持续不断地灌进电脑这两种需求对应的工程量差异很大。前者你只需要实现一个简单的 MAC UDP 发送通路时钟跑通、数据能发出去、PC 端能收到即可板卡资源占用可能不到 5%。后者则要考虑数据缓冲、突发处理、流控、CRC 计算吞吐率、MAC 层回环效率甚至要把 DMA 引擎搬进来让 CPU 或者逻辑直接从 DDR 里搬数据往以太网口上塞。我在做这个实验时的目标定为“通且快”——先保证链路能通再逐步优化吞吐。整套方案选用了 UDP 协议而不是 TCP原因很直接UDP 协议栈在 FPGA 里实现简单得多无需维护连接状态、滑动窗口、重传机制只需把 IP 头和 UDP 头按规范拼好、校验算对就能跑。对于图像传输、ADC 采样数据上传这类丢几包可以接受的场景UDP 是性价比最高的选择。提示如果你后续要考虑“可靠传输”也建议先从 UDP 通路打通开始再在应用层加重传逻辑。直接在 FPGA 里写 TCP 状态机调试成本会翻好几倍。2.2 开发板与芯片选型Artix-7 和 Cyclone 系是主流千兆以太网实验对 FPGA 本身的资源要求不高但对外部 PHY 芯片和接口定义有要求。我使用的是黑金系列的开发板主控芯片为 Xilinx Artix-7 系列具体型号为 AX7A035PHY 芯片为 Realtek 的 RTL8211E通过 RGMII 接口与 FPGA 相连。选 Artix-7 的原因很实在片上资源足够跑一个 MicroBlaze 软核加各种外设同时价格也不算离谱社区资料丰富遇到问题几乎都能搜到答案。如果你用的是 Cyclone IV 或 Cyclone V 系列也完全没问题千兆以太网这部分逻辑本身与芯片厂商无关只是例化 PLL、IDDR、OSERDES 等原语时写法略有差异。关于 PHY 芯片我强烈建议你在做实验前先确认清楚板子上用的型号。我最初以为板载 PHY 是千兆的结果调试了很久速率上不去后来才确认芯片是 10/100M 的。这个坑浪费了小一周时间。RTL8211E、88E1512、YT8531 这些是千兆板卡常见的 PHY拿到板子先查数据手册确认支持 1000BASE-T。2.3 时钟架构千兆为什么是 125MHz 和 2.5MHz 一起出现千兆以太网基于 RGMII 接口时时钟频率是 125MHz数据在上升沿和下降沿各采一次也就是 DDR 模式。这意味着每个时钟周期能传 2 bit8 根数据线加起来正好是 1000Mbps。而 100M 模式下时钟是 25MHz10M 模式下是 2.5MHz。很多初学者在这里会犯迷糊为什么 PHY 芯片配置成千兆模式后FPGA 内部逻辑频率要跑到 125MHz因为 RGMII 的发送时钟 TXC 就是 125MHz数据线 TXD[3:0] 与 TX_CTL 都在 TXC 的上下沿变化FPGA 侧需要用 IDDR 原语把 DDR 数据转成单沿数据处理频率自然就是 125MHz。我在实验中采用了两套时钟方案125MHz 主时钟由板载晶体提供连接到 PHY 芯片的时钟输入PHY 内部 PLL 锁定后输出给 FPGA用户逻辑时钟由 MMCM/PLL 从 125MHz 分频/倍频出 125MHz用于 MAC 层及上层协议逻辑。注意FPGA 内部的逻辑设计尽量不要直接使用 PHY 芯片输出的 RX 时钟作为全局时钟因为跨时钟域和路径延迟不可控。正确做法是让 RX 时钟只驱动 IDDR 采样的那一段逻辑采样后的数据再用异步 FIFO 同步到全局时钟域。3. RGMII 接口与 MAC 层实现从引脚到帧的完整链路3.1 一分钟看懂 RGMII4 根数据线怎么传 8 bitRGMII 是 Reduced Gigabit Media Independent Interface 的缩写把传统 GMII 的 8 根数据线、2 根控制线压缩成了 4 根数据线加 1 根控制线靠时钟上下沿复用。具体来说TXD[3:0]发送数据上升沿发送低 4 位下降沿发送高 4 位TX_CTL发送控制信号上升沿表示 TX_EN下降沿表示 TX_EN 异或 TX_ERTXC发送时钟频率 125MHzDDR 模式RXD[3:0]、RX_CTL、RXC接收侧对应信号。这个设计最大的优点就是引脚少但代价是时序收敛难度增加。尤其是数据线相对于时钟线的延迟关系必须处理好这直接决定了你能不能稳定收发。在 FPGA 内部处理 RGMII 数据必须使用 IDDR 原语把 DDR 数据转成两个单沿数据。Xilinx 7 系列代码如下// RGMII 接收侧IDDR 采样 wire rx_clk; // PHY 输出 125MHz RXC wire [3:0] rx_data; wire rx_ctl; reg [3:0] rx_data_l; reg [3:0] rx_data_h; reg rx_ctl_l; reg rx_ctl_h; always (posedge rx_clk) begin rx_data_l rx_data; rx_ctl_l rx_ctl; end always (negedge rx_clk) begin rx_data_h rx_data; rx_ctl_h rx_ctl; end wire [7:0] rx_data_8b {rx_data_h, rx_data_l}; wire rx_valid rx_ctl_h rx_ctl_l; // 正常数据时 TX_EN异或0 1这个方案的优点是逻辑简单在主频不高的 FPGA 里完全够用。如果需要更严格的时序约束可以用 Xilinx 的 IDDR 原语并把时钟相位调整 90 度确保数据稳定采样。但从实测来看如果布线不是特别差上面的代码在 Artix-7 上稳定跑是没问题的。3.2 为什么数据要“错相”接收RGMII 的时序坑这里有一个非常关键、也是无数人踩坑的点RGMII 规范里数据相对于时钟是有偏斜的。发送侧要求 FPGA 在发送数据时把数据延迟约 2ns 再输出这样接收端PHY 芯片在时钟边沿采样时数据已经稳定。接收侧则反过来PHY 芯片输出数据和时钟时数据比时钟提前约 1.5~2nsFPGA 如果直接在时钟边沿采样很可能会采到数据翻转的瞬间。解决方法通常有两种在 FPGA 内部对 RXC 时钟加 90 度相移再采样对 RX 数据加约束延迟让数据对齐到采样窗口中央。我实测下来最稳妥的是第一种用 PLL 把 RXC 做 90 度相移相移后的时钟驱动 IDDR 采样。这样数据和时钟的建立保持时间余量会非常充裕。Xilinx 下可以直接用 IDELAY 或 MMCM 的 CLKOUT 相位配置做到Altera 系可以在 PLL 设置里把时钟相移设为 90 度。注意很多 PHY 芯片自带内部延迟模式比如 RTL8211E 可以通过寄存器配置开启 RX/TX delay如果板子的硬件设计已经开启了内部延迟FPGA 端再做 90 度相移会导致时序错乱。调试时先看 PHY 的寄存器状态再决定要不要软件补偿。3.3 MAC 发送状态机从 FIFO 数据到以太网帧MAC 层是整个链路里逻辑最重、也最容易出差错的部分。我采用的架构是上层逻辑把待发送数据写入发送 FIFOMAC 发送状态机从 FIFO 读出数据按以太网帧格式逐字节输出。一个标准的以太网帧结构如下前导码7 字节 0x55用于物理层同步帧起始定界符1 字节 0xD5目的 MAC 地址6 字节源 MAC 地址6 字节长度/类型字段2 字节IPv4 时为 0x0800数据载荷46~1500 字节FCS 校验4 字节 CRC32。MAC 发送状态机状态划分如下IDLE等待发送请求PRE发送前导码和帧起始定界符MAC_HEAD发送目的/源 MAC 地址和类型字段DATA从 FIFO 读取数据并逐字节发送PAD如果数据不足 46 字节补零CRC发送 CRC32 校验值DONE拉高发送完成信号返回 IDLE。代码框架// 简化版 MAC 发送状态机 parameter IDLE 4d0; parameter PRE 4d1; parameter MAC 4d2; parameter DATA 4d3; parameter PAD 4d4; parameter CRC 4d5; parameter DONE 4d6; reg [3:0] state; reg [7:0] tx_byte; reg [15:0] byte_cnt; always (posedge tx_clk or posedge rst) begin if (rst) begin state IDLE; tx_byte 8h00; byte_cnt 16d0; end else begin case (state) IDLE: if (fifo_empty 1b0) state PRE; PRE: begin if (byte_cnt 7) tx_byte 8h55; else if (byte_cnt 7) tx_byte 8hD5; else begin tx_byte dst_mac[47:40]; // 首个 MAC 字节 state MAC; end byte_cnt byte_cnt 1; end // ... 后续状态类似 endcase end end这里有个细节CRC32 的计算是 4 字节为单位的而且以太网 FCS 计算覆盖的是目的 MAC 地址到数据载荷不含前导码和帧起始定界符所以 CRC 计算模块要与发送状态机同步启停。很多初学者把前导码也计算进去了导致 PC 端网卡直接丢弃帧。4. UDP/IP 协议栈裁剪FPGA 里不跑 Linux 也照样能发包4.1 为什么我推荐 UDP 而不是 TCP前面已经提过我做的是图像数据高速上传的场景UDP 是首选。理由再展开一下TCP 有连接管理、序号确认、窗口更新、拥塞控制这些状态机全部塞进 FPGA 逻辑里资源开销大、时序收敛难UDP 无状态只需构建包头数据直接塞进去发即可对于局域网点对点传输UDP 的丢包率在千兆网线下很低实测 1 米网线丢包率几乎为 0如果应用层需要可靠传输可以自行加序列号、重传机制控制灵活。这个取舍和做嵌入式 Linux 网络编程完全相反但在 FPGA 资源受限的场景下非常实用。4.2 ARP 请求处理别让它成为“ping 不通”的元凶在能发 UDP 包之前PC 和 FPGA 得先互相知道对方的 MAC 地址。PC 在发包前会先发 ARP 请求询问“192.168.1.10 的 MAC 地址是多少”如果 FPGA 不回应 ARPPC 永远不知道 FPGA 的 MAC后续 UDP 包根本不会发出来。所以一个能联通的 UDP 通路至少要包含 ARP 响应模块。流程是接收逻辑解析出目的 MAC 为广播地址FF:FF:FF:FF:FF:FF、协议类型为 0x0806、操作码为 1ARP 请求的帧提取发送方 IP 和 MAC构造 ARP 应答帧应答帧的目的 MAC 填入请求方 MAC源 MAC 填本机 MAC操作码填 2将应答帧交给 MAC 发送模块发出。这个模块调试时可以用 PC 端 ping FPGA 的 IP 来验证。如果 ARP 模块没写好ping 的表现就是“请求超时”但 Wireshark 里能看到 PC 发了大量 ARP 请求而 FPGA 无响应。4.3 IP 头与 UDP 头的构建校验和是个容易忽略的细节IP 头共 20 字节需要填写版本号4、头长度5、总长度、标识、生存时间64、协议号UDP 为 17、源/目的 IP以及首部校验和。UDP 头共 8 字节包含源/目的端口、长度和校验和。UDP 校验和是可选的PC 端默认会校验所以一定要算对。UDP 校验和的计算范围包括伪 IP 头源 IP、目的 IP、协议号、UDP 长度 UDP 头 数据按 16 位求和取反。这里我给一个建议在 FPGA 里做校验和不要在每一个包发送时用循环累加那样会在长包传输时导致吞吐量骤降。正确做法是边发送边累加发完数据后补上计算结果。对于一个 1400 字节的包边发边算和发完再回头算速度差异能到 3 倍以上。IP 首部校验和也一样只在 IP 头 20 字节内计算数据不参与。生成 UDP 包的 Verilog 逻辑片段// UDP 头构建假设发往 192.168.1.100:8080 // 源 IP 192.168.1.10源端口 9000 reg [7:0] udp_tx_data; // 待发送字节流 // UDP 长度 8 数据长度 wire [15:0] udp_length 16d8 tx_payload_len; // 发送序列IP头(20B) UDP头(8B) payload实测中我把 IP 头的总长度字段同 UDP 长度联动如果忘了更新这两个长度字段Wireshark 抓到包后会标记“短帧”或“长度异常”网卡可能直接丢弃。5. 实操记录从 QSPI 固化到 Wireshark 抓包全流程5.1 步骤一PHY 芯片初始化配置上板调试之前首先确认 PHY 的复位引脚拉高、时钟输入正常然后是 MDIO 接口配置。我用的 RTL8211E通过 MDIO 总线读写寄存器寄存器 0基本控制寄存器bit 6 为全双工使能bit 13 为速率选择1000M 时配置为 0b11寄存器 4千兆控制寄存器bit 8 为 1000M 全双工使能寄存器 0x1F芯片特定配置寄存器可能包含 RGMII delay 配置。MDIO 驱动时序很简单MDC 时钟 2.5MHzMDIO 数据在上升沿采样。状态机按协议发前导码、操作码、寄存器地址和写数据即可。如果不想自己写直接用厂商提供的 PHY 配置参考代码也可以但强烈建议你至少读懂配置流程出了问题才知道查哪。5.2 步骤二先把“回环”弄通写完整的上层协议栈之前先做一个最简单的测试把 RGMII 接收的数据原样送回发送端。也就是 PHY 接收什么MAC 就发什么。这样做的意义在于只需要验证时钟采样是否正确、IDDR 数据拼接方向对不对、PHY 是否有数据输出就能快速定位是物理层问题还是上层逻辑问题。我当时是让 FPGA 处于PHY 芯片内部回环模式通过 PHY 寄存器配置开启数据从 MAC 发出去PHY 自动绕回接收侧。如果回环模式收发一致说明 FPGA 与 PHY 之间接口时序没有问题问题大概率在上层协议栈。5.3 步骤三PC 与 FPGA 直连开始抓包确认回环通过后直接拿网线把 FPGA 开发板和 PC 连起来不需要交换机。设置 PC 网卡 IP 为 192.168.1.100FPGA 静态 IP 为 192.168.1.10然后 Wireshark 监听网卡。做这个实验时我发现一个非常关键的技巧Wireshark 抓包时一定要关掉 PC 端防火墙否则 ARP 回应到了网卡也被系统拦截表现为“收到但无响应”。从 FPGA 发 UDP 包到 PCPC 端 Wireshark 显示该包从 PC ping FPGA 的 IPWireshark 显示 ARP 请求和应答这条通路就算打通了。5.4 步骤四用 ILA 查内部信号不要对着网线发呆如果 Wireshark 什么都抓不到立即把 ILA 逻辑分析仪核加进去抓 FPGA 内部的关键信号。我会把以下信号全部拉出来PHY 输出 RXC 是否有 125MHz 时钟RX_CTL 是否有有效电平解出来的 rx_data_8b 是否是前导码 0x55MAC 状态机停在哪个状态发送 FIFO 是否为空/满。大多数情况下问题在前三个信号。如果 PHY 输出时钟都没有检查晶振、PHY 复位如果 RX_CTL 拉不高检查网线质量、对端是否正常发包如果 rx_data_8b 不是 0x55检查 PHY 是否配成千兆模式或者数据位是否颠倒了。这一步做好比对着 Wireshark 盲猜快得多。我从“Wireshark 抓不到包”到“定位是 PHY 配置问题”用 ILA 只花了 10 分钟。6. 吞吐量优化与难题排查从 100M 跑到接近线速6.1 为什么实测只有 100Mbps烧掉的那一晚上第一次把数据通路调通后我用 iperf 测吞吐量结果稳定在 100Mbps 左右死活上不去。检查了 PHY 寄存器、配置了千兆模式甚至换了网线、换了 PC 网卡依然纹丝不动。后来发现是 MAC 发送模块的设计问题每次发送完一帧后状态机会等 12 个时钟的帧间隙IFG再检查发送 FIFO 是否有新数据。看起来完全符合以太网规范但问题在于 MAC 控制信号与 FIFO 读请求之间多插了几个周期的延迟导致实际帧间隙被拉长了几百纳秒。对于 1000M 以太网每增加一个周期就会损失一小块带宽。优化方案是两个状态机在 DATA 状态下拉高 FIFO 读请求而不是在每个状态切换时重新发起读操作预读策略在上一帧的 CRC 发送阶段提前检查 FIFO 并准备好下一帧的状态CRC 发完即可无缝衔接。改完这两个点吞吐量从 100Mbps 直接跳到 850Mbps 以上。如果你的吞吐量卡在一个看起来“神秘”的值先检查帧间隙、CRC 计算和数据通路的停顿点。6.2 调优顺序时钟优先逻辑其次最后才看 PHY我在多次排障后总结出一个调优顺序分享给你第一优先级时钟。125MHz 是否稳定、RXC 相移是否正确、数据采样窗口是否够宽第二优先级帧格式。前导码、MAC 地址、类型字段、长度字段、CRC 有没有错第三优先级状态机吞吐。有没有不必要的等待周期、FIFO 读写是否匹配、信号有没有跨时钟域的亚稳态风险第四优先级PHY 芯片寄存器配置。速率、双工模式、延迟补偿。大部分“千兆不通”的问题80% 出在前两级真正 PHY 配置错误的反而很少。不要一开始就怀疑 PHY先从自己的逻辑找问题。6.3 板级调试速查表常见症状与对应排查点我整理了一张速查表是我自己反复用过的症状最容易忽略的原因排查动作ping 不通ARP 模块未响应ILA 抓 ARP 请求帧是否被正确解析Wireshark 看不到任何包网卡防火墙拦截关闭防火墙后重试能收到包但长度不对IP/UDP 长度字段未更新检查长度点与 FIFO 计数对比吞吐量固定在 100MMAC 状态机空闲间隙过长检查帧间隙计时逻辑数据错位或乱序IDDR 高低位拼接方向反了对比前导码 0x55 与 0xD5 顺序偶发性丢包跨时钟域亚稳态检查 FIFO 读写时钟是否正确提示如果 PC 端显示“网络电缆被拔出”大部分时候是 PHY 芯片没初始化好。检查 PHY 复位时序、VDDIO 电压、MDIO 是否配置成功。7. 后续扩展方向与经验总结千兆以太网通路打通后可以扩展的方向非常多。我自己接下来做的是把图像采集模块和 DMA 引擎接进来让数据从 DDR 直接搬到以太网 MAC 发送 FIFO形成一条完整的图像传输链路。这个方向如果你的项目需要高速搬运数据是绕不开的一步。另外很多做高速 ADC 采集的朋友也可以用类似架构ADC 采样数据经跨时钟域同步后写入 DDR 环形缓冲再按帧打包通过 UDP 上传和我的方案在整体架构上是完全一致的。区别只在于上游数据的类型和速率。这里也分享两个个人体会算是我踩过最多的坑第一个是不要迷信网上的“直接用”代码。FPGA 开发中即使代码功能完全一致在不同板卡上由于引脚分配、时钟频率、PHY 芯片型号不同表现可能完全不同。参考别人的代码可以但一定要一条条对照自己的数据手册和原理图。第二个是先把 PHY 数据手册读三遍再动手。RGMII 延迟补偿方式在不同 PHY 芯片上有差异有的芯片是内部固定延迟有的需要通过寄存器调整。一个寄存器位搞错整个链路就是不通而且这种问题盲调非常费时间。如果这个实验之后你还想做更深入的折腾我建议顺序是先实现 ARP ICMP 应答让 ping 通再做 UDP 发送再实现 UDP 接收和回环最后做 DMA 集成。每一步都有独立的验证方法不要一口气全写完再上板那样出了问题都不知道从哪查起。FPGA 千兆以太网这条路说难不难说简单也绝不简单。关键在于把每一层拆开来看逐层验证逐步逼近。希望这份完整记录能让你少走几个弯早点看到自己板卡上飞起来的数据包。
返回列表