资讯动态

FPGA 100G UDP协议栈移植实战:从开源方案到线速收包

发布时间:2026/9/8 13:21:36 来源:尧图企业网站定制
先把结论放在前面这个项目本身不复杂但真正的复杂度全藏在“移植”两个字里。我从拿到一块带 QSFP28 光口的 UltraScale 板卡到把开源 100G UDP 协议栈跑起来、双侧验证线速收包前后折腾了大概两个礼拜。期间踩过了光模块兼容、时钟域处理、校验和丢弃、iperf3 参数误导等等一堆坑。这篇文章就把整个过程拆开讲清楚包括环境选型、代码移植、上板测试、问题排查尽量做到你照着走一遍就能复现。适合看这篇文章的人有两类一类是刚接触 FPGA 网络方向、想快速把 UDP 协议栈跑在高速链路上的同学另一类是已经在做 100G/200G 数据面开发想用开源方案做原型验证的工程师。我会尽量把“为什么这么做”也写出来而不是只给一份能跑通的代码。1. 项目概况与整体设计思路1.1 为什么选 100G UDP而不是自己写协议栈FPGA 做网络数据处理最早大家都是从千兆、万兆 Ethernet 开始自己写一个 MAC 控制器、一个 UDP 解析状态机调通就很有成就感。但到了 100G 这一档局面完全变了数据位宽从 32bit、64bit 提升到 512bit时钟频率从 125MHz 拉到 300MHz 以上光模块从 SFP 变成 QSFP28MAC 的物理编码子层还涉及 64B/66B 编码、RS-FEC 这些细节。如果每个模块都从零写光是把 PCS/PMA 调稳定就得花掉好几周。所以业界主流做法是物理层和链路层直接复用成熟的 IP 或者开源库UDP 协议解析只是薄薄一层逻辑自己只专注于业务功能。100G UDP 的好处在于UDP 本身没有连接状态、没有确认重传协议处理简单得就像一个“包裹分发中心”——查一下目的端口把 payload 拿去用就行。对 FPGA 来说这意味着可以做到很深的流水线每周期吞吐固定容易达到线速。这次移植的另一个动机是验证“开源方案在 100G 场景下到底能不能跑”而不是听别人说可以。很多开源以太网 IP 库在仿真里表现得很好看一上板就暴露出跨时钟域、时序违例、手上协议处理不完整等问题。我这次选择的是社区里维护比较活跃的 verilog-ethernet 项目它有 10G/25G/40G/100G MAC 的 Verilog 实现以及 ARP、IP、UDP 的 offload engine正好覆盖我想要的完整链路。1.2 开源方案选型与数据通路设计我最终采用的方案结构可以理解为一条单向数据流水线QSFP28 光模块 - 100G MAC (Xilinx CMAC IP) - UDP Offload Engine (开源) - 用户业务逻辑CMAC 负责物理层和链路层输出标准的 AXI-Stream 接口数据位宽我配置成 512bit用户时钟大概在 300MHz 上下。开源协议栈这边拆成几个核心模块eth_mac_100g或者直接对接 Xilinx CMAC负责以太网帧的封装和解封装eth_arp负责处理 ARP 请求和响应保证主机能通过 IP 找到 FPGA 的 MAC 地址eth_ip负责 IPv4 头的解析和校验eth_udp负责 UDP 头的解析提取源端口、目的端口、长度信息。用户业务逻辑在本次测试里做的是“回环”把 FPGA 收到的 UDP payload 原封不动再发回给主机。这样一个简单的应用层就能同时验证 RX 和 TX 两条路径把 MAC 和 UDP 协议栈的问题暴露得干干净净。后续如果做实际项目只需要把这一层替换成 DMA 搬运到 DDR、或者做数据包过滤分发就能复用整条已验证的链路。这里也解释一下为什么不选商用 IP商用 UDP Offload IP 确实更完整还带 AXI-Lite 寄存器接口和中断控制但价格不便宜而且很多功能在这个阶段用不上。开源方案的好处是代码全可见出了问题能直接拉仿真波形一眼看到底调试效率高很多。代价是需要自己处理一些边界情况比如 UDP 校验和为 0、分片 IP 包等。2. 环境准备与硬件选型2.1 板卡、光模块与测试网卡这次用的板卡是一块基于 Xilinx UltraScale KU15P 的第三方开发板板载两个 QSFP28 光口没有 PCIe 接口纯粹做网络设备原型足够。开发板本身带 16GB DDR4但这次回环测试没有用到我后面会说在什么场景下才需要挂 DDR。光模块选了 QSFP28 的 100G SR4 光模块配 MPO 光纤跳线。这里有一个经验如果只是板卡和主机之间点对点测试其实我更推荐用 QSFP28 DAC 铜缆距离短、信号稳定、不用考虑光模块清洁问题还便宜。我在测试早期因为手头只有光模块踩到了信号质量导致的 CRC 错误后面排查了很久才锁定是光路信号问题。主机侧需要一张支持 100G 的网卡。我用的是一张 Mellanox ConnectX-5 的单口 100G 网卡插在服务器的 PCIe 3.0 x16 槽上。选网卡的时候注意两点第一驱动要支持你要用的 Linux 发行版Mellanox 的 mlx5_core 驱动在主流内核里都有第二网卡固件和驱动版本最好更新到较新版本老固件在对接非标准链路时经常有兼容性问题。2.2 工具链版本与 IP 配置Vivado 版本我用了 2021.2这个版本对 UltraScale 和 CMAC IP 支持得比较完善。工程语言选 Verilog仿真用的 Vivado 自带的 XSim因为工程不算大跑整个 UDP 回环的仿真也就几分钟。CMAC IP 的配置有几个关键参数我整理成了表格参数配置值说明Line Rate100G必须和光模块/网卡速率匹配Datapath Width512 bitAXI-Stream 数据位宽高速率下必须宽位宽RS-FECDisabled短距离直连测试先关掉减少变量PCS/PMA100GBASE-R标准的 100G 以太网物理编码子层RX Flow ControlDisabled回环测试用不到流控TX Flow ControlDisabled同上RS-FEC 这里多说一句100G 长距离传输比如 LR4 光模块通常需要开启 RS-FEC 才能保证误码率但板卡和主机在一个机房里用短光纤/DAC 互联时关闭 RS-FEC 能少一层编解码延迟也少一些排查点。前提是链路两端配置保持一致如果主机网卡是强制开启 RS-FEC 的那 FPGA 侧也必须开。CMAC IP 生成之后会自动带出 AXI-Stream 接口和几个时钟/复位信号。这里要特别注意的是CMAC 的 RX user clock 和 TX user clock 是独立的后面逻辑设计千万不能想当然把它们当成同一个时钟域否则上板会出现偶发的数据错乱仿真还很难复现。3. 移植落地从 RTL 到比特流3.1 源码结构与顶层搭建verilog-ethernet 项目的目录结构大致长这样verilog-ethernet/ rtl/ axis/ # AXI-Stream 通用组件FIFO、跨时钟域等 eth/ # MAC、ARP、IP、UDP 协议栈核心 lib/ # 一些通用库函数 example/ xilinx/ # 现成的 Xilinx 平台 demo synth/ vivado/ # Vivado 工程生成脚本我这次没有直接用 example 里的现成工程因为它的顶层是针对特定板卡的和我的 CMAC 实例化方式不一定匹配。更好的做法是自己搭一个顶层把开源库的 RTL 文件加进工程实例化 CMAC IP然后手工连接接口。顶层模块的信号接口最少需要这几组module udp_loopback_top ( input wire clk_tx_user, // CMAC TX user clock input wire clk_rx_user, // CMAC RX user clock input wire rst_tx_user, input wire rst_rx_user, // 从 CMAC RX 侧出来的 AXI-Stream input wire [511:0] s_axis_rx_tdata, input wire [63:0] s_axis_rx_tkeep, input wire s_axis_rx_tvalid, input wire s_axis_rx_tlast, input wire s_axis_rx_tuser, // 进 CMAC TX 侧的 AXI-Stream output wire [511:0] m_axis_tx_tdata, output wire [63:0] m_axis_tx_tkeep, output wire m_axis_tx_tvalid, output wire m_axis_tx_tlast, output wire m_axis_tx_tuser );这里 AXIS 的tkeep是 64bit对应 512bit 数据的每一个字节是否有效。CMAC 输出的tuser里带了一些错误标记比如 RX 侧的 CRC 错误、协议错误这个信号在调试阶段特别有用建议直接接到 ILA 探针上。顶层内部把 CMAC 的 RX 出口接到开源库的eth_udp_rx解析出 payload 后送入一个回环 FIFO再从eth_udp_tx拼上以太网头发回去。这个回环 FIFO 除了缓冲数据还做了一个重要的跨时钟域隔离RX 侧时钟进TX 侧时钟出避免两个时钟域直接连导致亚稳态。3.2 时序收敛与资源优化100G 数据路径上时序收敛是第一优先级。CMAC 用户侧接口我配置成 512bit 位宽实际跑起来用户时钟在 300MHz 左右。开源协议栈的逻辑不复杂理论上跑 300MHz 问题不大但在具体实现时我发现两个容易拖慢时序的点。第一回环 FIFO 如果直接用 Vivado 的普通 FIFO IP在 512bit 位宽下 LUTRAM 或者 BRAM 的选择有讲究。我用的是标准 BRAM FIFO配置成“First-Word Fall-Through”模式这样可以直接拿到dout的 valid 信号省一个寄存器的判断能压掉几个纳秒的路径延迟。第二eth_udp_tx模块里计算 UDP 长度字段用的加法器如果做在关键路径上长帧还好短帧会拖累整体频率。开源代码其实已经考虑到了这一点长度字段是两个周期之前就算好的但我第一次移植没仔细看硬是把长度计算和发送使能逻辑挤在同一个状态机里结果时序报告一片红。后来老老实实按原代码的流水线节奏走综合频率一下就上去了。最终资源占用方面纯协议栈部分只用了不到 2% 的 LUT 和 1% 的 BRAM剩下的资源全留给业务逻辑。这也是高速 UDP 方案的一个优势——协议处理开销极小根本不需要动辄几百万门的 IP。4. 上板测试验证方法与问题定位4.1 硬件连接与链路检查上板之前先把物理环境准备好。我用 DAC 线缆把 FPGA 板卡的 QSFP28 口直接连到主机的 100G 网卡省去光模块和光纤的折腾。上电后先看 CMAC 的status信号重点检查rx_block_lock和tx_block_lock是否为高。这两个信号代表 CMAC 的 PCS 层已经锁定到 100G 链路如果一直拉不起来多半是物理层的问题比如线缆没插紧、对端网卡 down 了、或者速率配置不匹配。主机侧先确认网卡识别到了口子$ ip link show enp3s0np0 8: enp3s0np0: BROADCAST,MULTICAST,UP,LOWER_UP mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000给网卡配一个静态 IP并且关闭可能干扰测试的自动协商相关功能。100G 网卡一般会在驱动里自动协商链路只要物理层正常ethtool enp3s0np0应该能看到Speed: 100000Mb/s。这一步最容易踩的坑是FPGA 板卡上电后 CMAC 默认没有输出信号主机网卡会一直认为链路 down。要确认 FPGA 的 bit 文件已经加载、并且 CMAC 的复位已经被正确释放主机的网卡状态才会变成 UP。我调试时遇到过好几次“主机网卡死活起不来”后来查到底层原因是 fpga 工程的复位逻辑里把 CMAC 复位拉得太久了释放复位的那一刻主机已经放弃了链路协商重启网卡就好。4.2 功能测试从内部回环到 UDP 透传链路起来之后不要急着跑业务先做 CMAC 内部回环测试。这个测试的目的是验证 FPGA 侧 TX/RX 数据通路的正确性完全绕开光模块和主机。CMAC IP 自带一个回环模式寄存器通过 AXI-Lite 接口把它设置成内部回环然后从主机侧向 FPGA 发 UDP 包FPGA 侧的 TX 路径会把包原样送回到 CMAC 的 RX 路径主机网卡就能收到自己发出去的包。此时 Wireshark 上会看到很多“自己发给自己的包”不要惊讶这恰恰说明 MAC 收发通路没有问题。内部回环通过后关闭回环模式进入正常收发。主机侧用 Scapy 构造一个简单的 UDP 包发到 FPGAfrom scapy.all import * sendp(Ether(dst02:00:00:00:00:10)/IP(src192.168.1.20, dst192.168.1.10)/UDP(sport12345, dport5001)/bhello_fpga, ifaceenp3s0np0)如果 FPGA 里的回环应用正常工作主机上同时跑一个 Wireshark 抓包应该能收到 FPGA 发回的 UDP 包。这里要注意抓包要监听在网卡物理口上不要开混杂模式以外的过滤否则可能漏掉一些异常包。如果这一步收不到回包第一步先看 ILA 里 CMAC 的 RX 接口有没有tvalid拉高如果没有说明包根本没进 FPGA 逻辑如果有再往 UDP 解析模块后面追看是 ARP 没回还是 UDP 端口匹配不上。我把常见的排查手段放在后面专门讲。4.3 性能测试iperf3 UDP 打流功能通了下一步就是看能不能顶得住线速压力。iperf3 是常用的打流工具UDP 模式下可以作为简单的压力源和服务端。先在主机上起服务端iperf3 -s -u -p 5001再起客户端以 80Gbps 的速率向 FPGA 打流iperf3 -c 192.168.1.10 -u -b 80G -t 30 -l 1472 -P 4这里-l 1472是 UDP payload 长度1472 是标准 1500 字节 MTU 减去 IPv4 头 20 字节和 UDP 头 8 字节后的最大值这样发出去的单包不会触发 IP 分片。-P 4表示 4 个并发流iperf3 新版本支持但老版本对 UDP 多线程支持不好你也可以直接开 4 个 iperf3 客户端进程。-b 80G给点余量不要打满 100G避免网卡或 PCIe 瓶颈导致结果失真。测试结果我这边记录下来的是持续 30 秒打流主机侧显示接收带宽稳定在 78.9 Gbps丢包率 0%。这个带宽已经比较接近 UDP payload 占线速的比例了因为 100G 以太网线速里本身就要扣除帧间隔、前导码、以太网头和 IP/UDP 头的开销能跑到 79G 说明 FPGA 基本做到了线速转发。再用几个不同的包长跑一轮目的是验证小包场景下的处理能力。64 字节小包是网络设备最怕的场景因为每秒包数会飙到 148MppsFPGA 逻辑如果每周期只能处理一个包在 300MHz 时钟下根本来不及。开源协议栈在小包场景下的表现取决于模块处理状态机的效率实测下来 64 字节包能跑到大概 80% 线速的包速率这个数据在纯软件协议栈里是望尘莫及的。4.4 长稳测试与数据校验功能测试和短时间打流通过后还要做长稳测试。我跑了 1 小时满带宽 UDP同时用 Wireshark 的统计功能定期看有没有 TCP 乱序、重传或者校验和错误。长稳测试暴露的问题和短时间测试完全不一样很多偶发的 CRC 错误、FIFO 溢出都要跑到十几分钟才能浮现出来。数据校验如果用 iperf3 的 UDP 模式它只统计包的个数和字节数不校验内容是否正确。要验证内容一致性我用 SCAPY 构造了一批带序列号的 packetFPGA 回环后在大批量数据里检查序列号有没有跳变结果全部连续确认回环路径上没有丢包和串包。顺带提一下 DDR 的话题。如果应用需要把 UDP payload 缓存到 DDR 再转发那一定要做 DDR 带宽的预算100G 双向就是 25GB/s 的读写压力DDR4 单通道理论带宽只有 25.6GB/s实际随机访问效率能到 70% 就算不错所以通常需要两到四通道 DDR4。这也是很多 100G 智能网卡板卡要做四通道 DDR 的原因。本次回环测试用不上 DDR不展开。5. 常见问题与排查技巧实录5.1 链路通了但收不到任何包先查这几处这是我最常被问到的问题。链路层 UP 只代表 PCS 锁定了不代表 MAC 能正确解出以太网帧。建议排查顺序用 ILA 抓 CMAC 的 RX AXI-Stream 接口看tvalid有没有拉高。没有拉高说明 MAC 收到的数据没进入用户逻辑查 CMAC 的rx_error相关信号是不是有 CRC 错误。如果tvalid有脉冲但后面的eth_udp_rx没有输出 payload查是不是 ARP 没有完成。FPGA 侧如果没有配置静态 ARP 表收到一个目的 IP 不认识的包时会先回 ARP 请求如果 ARP 请求没发出去或者响应没回来数据包会被丢弃。检查主机侧有没有正确配置 ARP。最简单的办法是在主机上加一条静态 ARParp -s 192.168.1.10 02:00:00:00:00:10这条命令把 FPGA 的 IP 和 MAC 绑定省去 ARP 协商这一环很多问题就能缩小范围。5.2 UDP 校验和不处理主机端疯狂丢包开源协议栈默认可以配置是否计算 UDP 校验和。我一开始图省事把校验和置 0 直接发出去结果主机网卡驱动在开启 RX checksum offload 的情况下会丢弃这些包表现为“包发出去了但收不到回包”。排查这个问题用 Wireshark 抓包最容易看出来如果抓到的包显示UDP checksum: 0x0000 (not verified)而主机协议栈不认这种包那就要么在 FPGA 侧正确计算 UDP 校验和要么把主机网卡的 RX checksum offload 关掉。正规做法是前者代码里把 UDP 校验和计算打开开销只额外占用几十个 LUT不痛不痒。5.3 时序违例尤其是跨时钟域导致的偶发错误100G 方案里RX 和 TX 时钟域独立如果代码里直接把 RX 数据信号交给 TX 时钟域的寄存器轻则时序违例重则出现极难复现的亚稳态错误。这类 bug 是仿真杀手因为仿真里的时钟是理想模型跨时钟域不会有真实延迟所以仿真全通过一上板就跑飞。凡是跨时钟域的接口一律经过异步 FIFO。verilog-ethernet 项目自带axis_async_fifo模块直接把 CMAC RX 出来的 AXIS 流进入 FIFO再从 FIFO 输出到 TX 侧数据和 valid/ready 信号全部同步问题就自动消失了。5.4 iperf3 参数没理解透导致测速结果失真iperf3 的 UDP 测试有个很容易误会的地方-b指定的速率是发送端的目标速率不是实际链路速率。如果你把-b设成 20G即使链路是 100G测出来也只有 20G这是符合预期的。另外iperf3 UDP 模式下客户端默认只看发送端统计如果想要看接收端视角要在服务端那侧看它的 Summary或者加-R反转测试方向。如果测出来带宽明显偏低先排除主机侧瓶颈PCIe 带宽够不够 100G网卡有没有 SR-IOV 或者 RSS 多队列驱动程序是不是ethtool -K关闭了某些 offload 功能这些都是纯软件侧的问题和 FPGA 无关。5.5 物理层误码率高的排查如果你发现 CRC 错误持续增长但逻辑层面找不到问题大概率是物理链路质量问题。先用ethtool -S enp3s0np0看网卡侧的rx_crc_errors计数如果增长很快检查线缆是否松动、光模块的清洁、有没有弯折过大的光纤。我遇到过换了根 DAC 线缆就彻底解决 CRC 问题的情况这类问题排查成本不高但很容易让人误判成逻辑 bug。写在最后这次移植做完一个很深的体会是100G 数据传输的瓶颈往往不在 FPGA 逻辑本身而在接口对齐和时钟域处理上。开源协议栈把 UDP 解包和组包的细节处理得比较完备但工程落地时真正让人熬夜的永远是那些仿真看不出来的边界情况。最后分享一下我常用的小技巧上板调试阶段在 CMAC 的 RX 和 TX 接口上各挂一个 ILA配置成 512bit 数据宽度深度 4096。抓拍条件设为tvalid tlast这样每次抓到的是一个完整的包。比较 RX 和 TX 的抓包波形如果两边数据完全一致基本可以确认转发路径没有丢改数据如果从某一段开始不一致就从那个位置往前逆推很快能找到是哪个模块出了问题。如果后续想把这条路用到实际项目里扩展的方向大致有三个一是加 DMA 路径把 UDP payload 直接搬到 DDR做好缓存管理二是加多队列分类按五元组或者自定义规则分流到不同处理通道三是考虑更低时延的方向比如绕过 MAC 层直接操作光模块接口。任何一个方向展开都是独立的大工程但有了稳定的 100G UDP 基座后面再盖楼就从容多了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价