FPGA上移植100G UDP协议栈:从Verilog到上板验证全解析 📅 发布时间:2026/9/5 9:05:23 👁 浏览次数: 1. 项目整体设计与技术选型思路1.1 为什么要在 FPGA 上移植 100G UDP 协议栈先说结论这不是一个“为了炫技”的项目而是一个很典型的工程场景——高吞吐数据采集系统需要把数据从板卡侧搬到服务器侧而 UDP 是最现实的选择。我手里的板卡主芯片是 Xilinx UltraScale 系列板载 QSFP28 光口MAC 层用的是 Xilinx 10G/25G/100G Ethernet MAC IP。原来的数据通路是 FPGA 采集完数据后先落 DDR再通过 PCIe 上报给主机。这个方案本身没问题但碰到“主机侧来不及消费”或者“数据是广播性质的、多路订阅”的场景时PCIe 的同步模型就很别扭。相比之下直接走光口打 UDP 包出去绕过 PCIe 枚举和驱动层对整个系统的解耦程度要高很多。但 100G UDP 移植跟常见的 1G/10G UDP 移植有本质区别。1G 以太网一个线速周期是 8ns留给逻辑处理的时间相对宽裕到了 100G数据位宽被拉到了 512bit核心时钟跑到 322MHz 左右取决于具体 MAC 配置每个时钟周期要处理 64 字节的数据。这意味着所有逻辑都得按“流水线化 寄存器切片”的方式来设计不可能再用 1G 时代那种“状态机等一拍再处理”的思路。还有个更实际的原因市面上不少 UDP 协议栈是带操作系统的软件栈比如在 Linux 里用 AF_PACKET 裸 socket 发帧或者基于 Lightweight IP 做裁剪。但我们的场景是 FPGA 端做纯硬件协议栈不跑 CPU 软核。所以这个项目本质上是把一个开源 RTL 协议栈工程移植到自己的板卡平台再完成上板验证的全过程。1.2 开源方案选型为什么选 Verilog Ethernet 组件先交代硬件环境方便后面对齐细节FPGAXilinx UltraScale KU15P光口QSFP28 x 1100G 模式MACXilinx CMAC IP100G Ethernet Subsystem参考时钟156.25MHz内存DDR4 2400T仅用于数据缓存控制方式MicroBlaze 软核 UART用于寄存器配置和状态监控协议栈部分我最终选了 Alex Forencich 的 verilog-ethernet 项目。这个项目在 GitHub 上开源了很久维护活跃最关键的是它的代码风格非常干净几乎每个模块都做成了独立可复用的形式。它包含完整的 Ethernet MAC、UDP offload、ARP、ICMP、RGMII/XGMII/CMAC 适配层可以直接接 Xilinx CMAC IP 的 AXI4-Stream 接口。这里插一句为什么不用 Xilinx 自带的 OpenUDP/IP。它确实也支持 100G但问题是它的体系偏重——带了完整的 AXI DMA、Descriptor Ring 管理、中断控制器一套下来资源开销不小。而我们项目只要一个纯粹的“从 AXI-Stream 到 UDP 帧”的转换通路不需要在 FPGA 里跑完整的 DMA 引擎。用一个裁剪过的开源 UDP offload 核心反而更贴合需求。另外verilog-ethernet 支持参数化配置可以在综合前把 ARP、ICMP、校验和计算这些模块按需开关。我实际裁剪之后LUT 占用不到整个芯片的 8%这对后续在同一个 FPGA 里继续塞业务逻辑来说非常重要。1.3 LiteOS 在项目里的实际定位看到热词里有“freertos移植”“liteos移植说明”顺便说下这个项目里软件的角色。方案早期考虑过在 FPGA 里放一个软核跑 RTOS 来管理 UDP 会话和 ARP 表后来权衡之后把与协议相关的功能都改为硬件实现RTOS 只保留最底层的寄存器配置和状态轮询。也就是说LiteOS 在这个项目里承担的角色很轻——串口命令解析、寄存器读写、简单的定时器调度。真正的高吞吐数据通路完全在硬件里跑不经过 CPU。这样设计的理由很朴实UDP 传输的实时性和吞吐要求决定了对中断响应、上下文切换的要求非常严格而 RTOS 的调度开销即使只有几十微秒在高吞吐场景下也可能成为瓶颈。纯硬件转发可以把单帧处理延迟压到纳秒级。不过移植 LiteOS 到 MicroBlaze 的过程本身比预想的顺利主要是 LiteOS 对 MicroBlaze 的支持在官方仓库里已经有基础版本需要改的只是链接脚本和中断向量表。具体的移植细节后面在问题排查部分再展开。2. 协议栈移植的核心细节2.1 CMAC 与 UDP offload IP 的接口对接这个环节是整个移植过程中最需要花心思的地方。Xilinx CMAC IP 的 AXI4-Stream 接口是 512bit 位宽内部 tkeep 信号 64bit 逐字节使能而 verilog-ethernet 的 UDP offload 核心默认位宽是 64bit8 字节。直接对接会导致位宽不匹配必须加一个简单的位宽转换适配层。我在两者之间插入了一个用 Xilinx Axis Data Width Converter IP 做的转换模块将 512bit 转换为 64bit 输出。这里有个隐蔽的坑CMAC 在发送方向要求 tlast 必须与帧末尾对齐并且 tkeep 必须精确表示有效字节数。如果 tkeep 出现“中间空洞”的非法模式MAC 会直接丢弃整帧并且不报错。UltraScale 的 CMAC 还有一个特点就是接收方向的 preamble 处理与常规 MAC 不同SFD 之后直接就是目的 MAC适配时需要留意。实际对接中我按照下面的流程做信号检查能够在仿真阶段就过滤掉大部分接口问题用 AXI-Stream VIP 产生随机帧长64B~1518B的激励送到 UDP offload 输入端。在 CMAC 用户侧接口抓取 tdata、tkeep、tlast 三个信号检查 tkeep 是否连续且与 tdata 有效字节对齐。仿真通过后再上板跑 PRBS 测试CMAC 内部自带的回环测试确认 MAC 层本身无 CRC 错误。2.2 时钟域与复位设计的处理100G 系统最大的隐性风险是跨时钟域。CMAC IP 的内部用户时钟是 322.265625MHz100G / 512bit / 8bit而 UDP offload 核心运行在 322MHz 的同一时钟域下这部分没有跨时钟问题。但 DDR 控制器接口和 MicroBlaze 的 AXI 总线运行在 250MHz存在明显跨时钟域。verilog-ethernet 的 UDP offload 核心对时钟要求不算苛刻但复位顺序要严格按 CMAC IP 的复位时序来——必须等 CMAC 的 tx/rx reset done 拉高之后再释放我们协议栈的复位。我自己踩过的坑是直接复用全局复位导致 CMAC 的 tx reset done 未完成时就开始发送 UDP 帧结果是链路偶尔能通、偶发不通非常难排查。后来改成用 CMAC 的 reset done 信号作为协议栈复位的释放条件问题立刻消失。具体实现上我写了一个简单的复位同步模块// 输入cmac_tx_reset_done, cmac_rx_reset_done, axi_resetn // 输出udp_resetn reg [3:0] reset_delay; always (posedge clk or negedge axi_resetn) begin if (!axi_resetn) begin udp_resetn 1b0; reset_delay 4d0; end else if (cmac_tx_reset_done cmac_rx_reset_done) begin if (reset_delay 4d15) begin reset_delay reset_delay 1b1; udp_resetn 1b0; end else begin udp_resetn 1b1; end end end上板实测下来这个复位方案稳定长时间跑流没有复现偶发断链问题。2.3 UDP 校验和到底要不要做这个话题争议挺大。很多人觉得 UDP 校验和是“可选项”实际上 IPv4 场景下 UDP 校验和确实是可选的全 0 表示未计算但 IPv6 场景是强制的。verilog-ethernet 的 UDP offload 核心内置了发送方向的校验和计算模块。它的做法是在帧发送过程中实时累加伪头部和 UDP 头部的 16bit 和在发送结束时把最终结果回填到校验和字段。问题在于回填发生在数据已经进入 MAC 的 AXIS 通道之后所以实际发送出去的帧包含两个版本的 UDP 头前 64 字节如果已经打进 FIFO回填就来不及了。这个模块单独使用没问题但接上 CMAC 的 AXIS 接口并开启了“立即转发”模式后就会出现“预期校验和”和“实际发送校验和”不一致的情况。我最终的做法是在发送方向不依赖 offload 核心自动回填而是由用户侧的帧组装模块预先计算好 UDP 校验和通过独立的 checksum 输入端口传给 offload 核心。虽然多花了一点 LUT但逻辑简单、可控避免了回填时序的不确定性。接收方向则反过来了CMAC 本身不做 IPv4 校验和验证需要 UDP offload 接收核心来验证。verilog-ethernet 的 rx engine 会逐字节计算校验和并在帧结束时输出一个标志位表示校验是否通过。实际测试中只要链路没有误码校验结果始终是正确的一旦出现误码它也能迅速丢弃坏帧不会把错误数据交给上层。3. 从仿真到上板的完整实现过程3.1 仿真环境的搭建与关键激励设计仿真阶段的目标不是“跑通”而是把问题留在仿真阶段解决否则上板后每一个问题都要烧录一版 bit 文件效率极低。我的仿真环境基于 Xilinx Vivado 2022.2 自带的 xsim另写了一个简单的 AXI-Stream 激励生成器。激励分三档单帧测试一次发一帧检查帧格式、校验和、目的 MAC/IP/端口是否正确。连续小包测试包长固定 64B验证背靠背帧间隔IFG是否满足以太网最小帧间间隔要求。混合包长测试随机包长验证 tkeep 处理的正确性。这里特别说一下 64B 小包测试。100G 以太网每秒钟最多能处理的 64B 帧数量有个硬上限计算公式是100Gbps / (64B 8B preamble 12B IFG) 100e9 / 84B约等于 148.8Mpps。这意味着发送侧每 6.7ns 就要输出一帧在 322MHz 时钟下大约每 2 个周期必须完成一次 tlast→tvalid 的连续握手。如果逻辑里有任何一个周期“打嗝”帧速率就会掉线。仿真时我专门盯着 tvalid 和 tready 的手握表确保帧间隔严格保持 12 字节以上。3.2 上板前的硬件检查清单上板前我列了一份检查清单逐项确认过才敢烧录检查项方法判定标准光模块激光器是否点亮读取 QSFP28 的 DOM 寄存器温度、电压、偏置电流是否正常CMAC 链路是否建立CMAC 内部 PCS/PMA 状态寄存器rx_link_status 1接收 sync status 为 OK参考时钟是否稳定观测 MMCM 锁定状态locked 信号拉高DDR 读写测试向 DDR 写入固定模式再读回连续 1MB 模式读写无误用户逻辑供电读取电源监控各 rail 电压波动 3%这里想强调一下FPGA 上 100G 调试最先要排除的就是光模块和物理链路问题。很多时候逻辑完全没问题但光纤没插紧、光模块的 DOM 读异常、或者对端交换机端口没起来就导致了“看起来像是 UDP 协议栈移植失败”。先把物理层抠清楚能省非常多时间。3.3 上板实测iperf3 打流和 Wireshark 抓包仿真通过后我开始真正上板验证。板卡通过 QSFP28 光纤连接到一个 100G 交换机交换机再接入一台有 100G 网卡Mellanox ConnectX-5的服务器。服务器侧的配置很简单就是给网卡配一个静态 IPsudo ip addr add 10.0.0.2/24 dev enp3s0 sudo ip link set enp3s0 up sudo ip route add 10.0.0.0/24 dev enp3s0测试分两个方向发送方向FPGA 作为 UDP 源端按设定好的包长和速率向服务器打流。服务器测速用 iperf3 的接收模式iperf3 -s -i 1同时在服务器后台跑 tcpdump 抓包验证帧格式。如果 iperf3 能持续显示 90Gbps 以上的接收速率且 Wireshark 里帧格式正确、帧间隔均匀说明发送方向基本没问题。接收方向服务器用 iperf3 作为发送端FPGA 作为接收端。FPGA 内部对收到的 UDP 帧做计数和错误统计通过串口打印。由于 FPGA 端不上报数据到主机接收方向的验证主要看三点接收帧计数是否持续增长CRC 错误计数是否为 0UDP 端口号、目的 IP 是否过滤正确实测下来发送方向 100G 满速达成接收方向也稳定在 99Gbps 以上。这个结果说明整个数据通路UDP offload 核心 CMAC 光纤 交换机 网卡没有瓶颈。3.4 关于项目关键技术指标的梳理前面列了一堆过程干脆把项目的关键指标统一成一张表方便大家做脚本化验收。指标设计目标实测结果吞吐率100Gbps 线速发送 99.2Gbps接收 99.8Gbps最小帧间隔12BIFG满足无超额帧UDP 校验和IPv4 全帧校验发送和接收均正确丢帧率0无误码条件长时间打流无丢帧延迟不高于 2us单帧转发约 1.2us从 AXIS 输入到光口资源占用控制在全芯片 10% 内LUT 7.6%FF 6.1%BRAM 21%资源占用里 BRAM 偏高是因为 CMAC 的 FIFO 开了较大的缓存深度如果对端容易出现反压可以考虑适当减小这部分的缓冲但要注意别把吞吐吃下来。4. 常见问题与排查技巧实录4.1 链路不通先从 CMAC 状态寄存器查起上板后第一件事不是看协议栈而是看链路有没有起来。CMAC 的 RX 状态寄存器能反映很多事情寄存器含义异常情况RX link status接收链路是否建立为 0 说明光信号或 PCS 未同步RX sync statusPCS 同步状态为 0 说明误码率过高RX CRC error count接收数据 CRC 错误数持续增长说明物理层或时钟不稳TX link status发送链路状态为 0 说明对端没有 RX如果 RX link status 一直为 0先排除光模块和光纤再查参考时钟最后看 PCS 配置是否与其对端一致比如 RS-FEC 模式是否对得上。我们曾经因为交换机端口强制设置的 FEC 模式和 FPGA 端不匹配导致 link 始终起不来改一致后问题立刻解决。4.2 ARP 不通的排查过程UDP ASIC 移植项目里ARP 是第一个要处理的“协议逻辑”。我们的 FPGA 端是发流端服务器知道 FPGA 的 MAC 才能回 ARP 请求而 FPGA 需要维护一张静态 ARP 表。实际测试时发现FPGA 发出的 ARP 请求服务器能收到但服务器回的 ARP 应答 FPGA 收不到——表现是 TCP/UDP 通信完全不通。经过排查发现是接收方向的 MAC 过滤模块把非本机 MAC 的帧全部丢弃了而 ARP 应答帧的目标 MAC 确实是 FPGA 的 MAC按理说不应该被丢弃。问题出在我配置的 MAC 地址是一个组播地址的低位导致过滤逻辑判断错误。改成标准单播 MAC 之后ARP 流程立即正常。这里给大家提个醒Xilinx MAC IP 的帧过滤默认是允许广播和组播帧的但如果你在数据通路里加了额外的 MAC 过滤逻辑务必确认它的地址位宽和字节序处理一致是端序问题别问我怎么知道的。4.3 DDR 缓存乒乓逻辑导致的数据错位FPGA 端接收 UDP 帧后先写 DDR再通过 DMA 读到逻辑里做业务处理。这个场景在热词搜索里对应“udp fpga图像处理”“fpga图像处理”这类需求——实际上高速图像采集系统大多也是这个数据通路。我们遇到的现象是前几帧数据完全正确之后偶尔出现 64 字节偏移的图像错位。排查下来的根因是 DDR 写地址的突发长度设置与 AXI 总线实际的写大小不一致导致相邻两帧之间产生了一个未对齐的偏移。修正方案是在写 DDR 之前把每帧的有效长度按 64B 对齐之后再做地址累加。虽然牺牲了一点存储空间每帧平均浪费不到 32B但换来的是地址整齐、逻辑简单排查难度大大降低。这个经验也适用于其他“DDR 上做帧缓存”的场景——对齐远比省空间重要。4.4 LiteOS 移植时 MicroBlaze 中断向量表问题这个坑相对隐蔽。MicroBlaze 默认的异常向量表入口只有一个 exception handler 指向而 LiteOS 的移植文档里通常要求把硬件异常和定时器中断分开处理。如果直接把 LiteOS 提供的 exception handler 接到全部异常上会导致定时器中断里响应了不该响应的外部中断引起系统跑飞。解决方法是修改 BSP 里的 vectors 文件对外部中断intc和异常exception分别建立向量入口。修改后 LiteOS 的 shell 能稳定接收串口命令定时调度也正常。这个部分教科书里讲得少实际项目里必须踩过才知道。5. 开源资源与新项目扩展5.1 优选开源协议栈资源上面提到的 verilog-ethernet 是首选GitHub 上直接搜这个关键词就能找到。它的 hdl/ethernet 部分提供了完整可综合的 UDP offload而且模块划分非常干净非常适合做二次开发。另外 Xilinx 官方提供 100G UDP 示例工程——在 Vivado 的 IP Catalog 里搜索 UDP能找到官方集成的 UDP/IP Offload Engine但因为版权和通用性的原因还是建议以开源代码为主官方 IP 为辅。如果项目里需要 10G 或者 25G 的 UDP 通路verilog-ethernet 同样支持只需要改 MAC 侧配置和时钟频率即可大部分逻辑可以复用。5.2 关于算法类应用的心得热词列表里有很多与“fpga信号发生器”“fpga图像处理”“卡尔曼滤波 fpga”“fpga biss-c”相关的内容说明很多读者关注的是 FPGA 上怎么做算法加速。这类应用的共同点是数据量大、计算密集而 UDP 通路恰好是为这种“大量数据搬移”服务的。如果后续要做图像采集 UDP 上送纯可综合代码依然是最靠谱的方案。速度要求高的滤波算法可以用 Xilinx 的 Vitis HLS 加速但传输链路还是建议用 RTL 写至少传输逻辑部分不要交给 HLS 去综合否则时序收敛会让人看到凌晨四点的屏幕。5.3 关于调试方法论的个人体会上板测试和仿真最大的区别是“时间成本高”一次综合布线加烧录短则十几分钟长的甚至要半小时。如果发现一个 bug 要靠反复烧录去试效率极低。我建议在项目初期就建立一套完整的寄存器观测体系把关键信号比如帧计数、错误计数、FIFO 水位、状态机当前状态全部映射到一组 AXI 寄存器上通过串口或者 JTAG 就能实时读取。上板时一旦发现异常先读寄存器缩小范围再针对性加 ILA集成逻辑分析仪抓波形。这个流程能帮你把每次上板的时间利用率翻倍。实际项目里我发现有一半左右的上板问题其实在仿真阶段都能复现。关键在于仿真激励要贴近真实场景——不要只发“标准好帧”多造一些边缘情况比如帧长到达上限、连续最短帧、tkeep 全零、跨时钟域毛刺等。把这些情况都在仿真里跑过了上板之后反而轻松。最后再说一个个人的心得100G UDP 移植这个项目技术难度不算深但涉及的知识面特别广——以太网协议、FPGA 时序收敛、跨时钟域设计、光模块物理层、DDR 存储控制、RTOS 移植、性能调优每一样都要碰。如果你正在做一个类似的项目建议按“物理链路→MAC 层→协议层→上层业务”的顺序分层验证每层确认无误再进下一层这样能最大限度避免“多层问题叠加”时完全不知道从哪里排查的困境。祝大家也能顺利点亮自己的 100G 光口。