开源100G FPGA UDP协议栈移植与VCU118上板测试实战

发布时间:2026/9/8 16:47:35
开源100G FPGA UDP协议栈移植与VCU118上板测试实战 1. 项目背景与整体思路拆解1.1 为什么偏偏是100G UDP说个挺有意思的现象。前几年大家做FPGA高速网络10G就足够用了。40G刚出来那会儿很多团队觉得这就是天花板。结果视频业务、AI集群、数据中心无损网络一铺开25G、100G直接成了主流配置。我自己第一次接触100G的时候也嘀咕过这玩意儿真有必要吗后来真上手才发现100G以太网根本不是“带宽翻倍”这么简单——它的时钟架构、MAC处理方式、用户接口位宽和50G/40G都有本质区别甚至可以说是一个全新的设计思路。回到这个项目本身。标题是“开源100G FPGA UDP移植上板测试”说白了就是两件事第一找一套成熟的开源100G UDP协议栈移植到自己的FPGA开发板上去第二真刀真枪地上板测试让数据跑起来验证功能和性能。但如果你把100G以太网拆开看这里面的门道其实很深。先纠正一个常见误区。很多人以为100G UDP就是把MAC从10G改成100G用户逻辑不用动。这个想法害死人。100G以太网的MAC-PCS接口通常是512bit或者256bit位宽用户逻辑直接对接这个位宽的话时钟就变得极其难受。比如512bit位宽、322MHz时钟才是100G满速这哪是普通逻辑能吃得消的。所以成熟的100G UDP方案用户侧接口通常是512bit、156.25MHz或者322MHz双时钟域设计中间必须有FIFO做跨时钟域和速率匹配。再说UDP协议栈本身。你以为是简单的状态机加CRC计算错了。100G速率下发送侧要做到每个用户时钟周期都能接收新的UDP包接收侧要处理乱序、校验、FIFO溢出保护还要考虑ARP、ICMP等辅助协议。协议栈的架构直接决定性能上限。我们这次用的是Xilinx官方开源的XUP VVH参考设计改出来的方案加上了一套商用协议栈做对比最后还是选了开源的这套。1.2 开源方案的选型与对比开源FPGA UDP协议栈其实不多。真正能跑到100G、又有完整TCP/UDP协议支持的基本上就是Xilinx的XUP-VVH开源工程、开源社区里一些以太网MAC设计以及某些厂商的评估板例程。这几种我都做过分析简单说说各自的优缺点。Xilinx XUP-VVH是官方维护的开源工程代码在GitHub上能直接找到。它最大的优势是完整从CMAC到UDP/TCP协议栈、DMA引擎、PCIe接口全都有跑在VCU118、Alveo U250这些高端板卡上没问题。缺点也比较明显代码量很大板级约束文件针对性太强换块板子要改的地方多而且对新手特别不友好光是时钟复位那一套就够喝一壶的。另外还有一些轻量级方案比如某些工程师维护的纯RTL UDP栈只支持ARP/ICMP/UDP没有TCP。这类方案优点是代码少、结构清晰、容易看懂移植到新板卡上很快但问题也不少。验证不充分是最大痛点很多都在10G速率下验证过100G根本没跑过另外就是没有DMA引擎收发数据得靠用户逻辑自己搬运性能天花板低。我这次移植用的是Xilinx官方工程里拆出来的用户UDP接口示例加上了自己写的收发FIFO管理逻辑。选它因为官方代码经过大规模验证协议栈本身的bug少主要精力可以放在“怎么接自己的板子”上。实际跑下来也确实如此最耗时间的不是UDP协议本身而是时钟复位、PHY配置、板级约束这些周边环节。1.3 移植目标板卡与环境说明这次移植所用的板卡是Xilinx VCU118评估板FPGA芯片是Virtex UltraScale VU9P。板载100G光口走QSFP28接口光模块用的是Finisar的100G SR4。开发环境是Vivado 2020.2工程模式用的非工程批处理流程方便自动化编译和版本管理。先把这个组合说清楚很有必要。VCU118这块板子在FPGA高速网络开发里属于非常经典的选择VU9P有足够多的收发器和逻辑资源CMAC硬核数量够跑100G完全不成问题。但你也别以为官方板卡就好移植Xilinx参考设计默认假设你用官方SDK和落定的约束文件如果你要自己改时钟方案比如用板上可编程时钟芯片输出一个非标准的频率那你得自己写约束、自己配置芯片这个工作量还是有的。软件环境方面我用了Vivado 2020.2版本原因是这套版本对Virtex UltraScale的CMAC支持比较成熟SDK配套的驱动和例程也都齐全。另外2019.2和2020.1也能用但新版本Vivado对CMAC的IP核心有改动旧版本的工程直接升级容易出问题。如果你打算复现这个项目建议直接用和我相同的版本少很多坑。2. 移植过程的核心细节与实操要点2.1 工程结构梳理看懂官方代码再动手拿到开源工程的第一件事不是打开Vivado点综合而是花时间把工程结构搞清楚。我一开始也犯过毛躁的毛病直接跑综合结果几百个错误冒出来吓死人。后来学乖了花了一整天时间读代码结构之后移植顺畅得多。官方工程顶层通常叫xxx_top.v或xxx_shell.sv核心模块包括CMAC IP核配置负责100G MAC和PCS层这是最底层的物理接口UDP协议栈处理UDP/IP/ARP/ICMP协议对外暴露简单的收发接口DMA引擎可选用于PCIe传输场景用户接口一组AXI-Stream或者自定义接口给用户逻辑用我这次的重点放在“用户接口到自定义逻辑的对接”上。因为工程里的DMA引擎是给PCIe用的如果我们的应用不走PCIe直接让用户逻辑收发UDP数据包那这一层就要自己写。所以移植第一步是识别哪些模块保留、哪些模块砍掉。简化后的工程结构大概是这样top ├── cmac_wrapper // CMAC IP实例化时钟复位 ├── eth_udp_stack // UDP协议栈核心收发都在这 ├── user_interface // 自己写的用户收发接口接FIFO ├── pkt_gen // 测试用发包或者回环 └── clk_rst // 时钟管理复位同步模块职责必须清楚。UDP协议栈是核心千万别动它最多做参数化配置。自己的逻辑都是围绕它打的出了问题好定位。2.2 CMAC与光模块配置容易翻车的重灾区100G以太网离不开CMAC硬核。Vivado里叫CMAC专门处理100G MACPCS支持RS-FEC、各种自适应均衡、PTP时间戳。配置CMAC有一个最容易翻车的点参考时钟频率和PHY接口配置稍有差错就是link up不了。CMAC的参考时钟一般是155.25MHz或者322.265625MHz这个取决于你选择的线速率。100G SR4光模块用的是100GBASE-SR4线速率是26.5625Gbps四路加起来106.25Gbps算上编码开销。所以CMAC配置里线速率选26.5625G参考时钟选155.25MHz接口位宽选512bit时钟输出大概是322MHz。这里有个细节要注意CMAC输出的user clock频率不等于线速率除以位宽那么简单。因为还有FEC和编码开销实际用户逻辑的数据吞吐量要小于线速率。CMAC IP核会自己计算并输出正确的user clock频率你只要在约束文件里把时序约束写对就行。光模块这一头也有坑。QSFP28模块有I2C接口要通过I2C读取模块信息比如厂商、型号、温度、光功率。100G模块协议比较复杂很多时候上不了线不是因为FPGA代码有bug而是光模块没配置对或者光路有问题。我遇到过一种情况模块插上去link就是不起来查了半天发现是光模块的APC自动功率控制没收敛两个光口对接的时候发射功率倒是正常接收侧光功率太小触发接收告警。后来换了根跳线重新插拔几次才稳定。2.3 时序与复位设计哪些坑我替你踩了时序问题是100G FPGA设计里绝对绕不开的大山。512bit位宽、322MHz时钟意味着逻辑必须严格流水任何组合逻辑深度过深都可能造成时序收敛不了。我第一个版本的用户接口逻辑里在接收路径上加了一个大范围的字节匹配逻辑结果时序一团糟最差负裕量达到负数。后来花了两个晚上做流水切割把一个周期的大组合逻辑拆成三级流水时序才收敛。这个经验很重要100G下别想着牺牲时钟频率迁就逻辑而是要让逻辑适应时钟该加流水就加不要心疼延迟。复位设计也在移植中占了很大比重。CMAC和UDP协议栈对复位时序有严格要求官方代码里通常有标准的复位序列先复位CMAC等tx_reset_done和rx_reset_done拉高再释放协议栈复位。我见过很多人在移植时图省事直接把复位信号用按钮控制结果链路起来了但数据一直跑不通检查半天才发现是复位释放时序不对。整理一下我的复位设计经验用MMCM/PLL的locked信号作为主复位的源头不要用外部异步复位直接驱动每个模块的复位信号必须在模块内部做同步处理至少两级同步器协议栈复位释放顺序严格按照官方时序不要自作聪明调试时加一个复位状态寄存器通过ILA观察各模块的复位完成标志方便定位问题2.4 收发FIFO与用户接口的对接方案官方UDP协议栈暴露的用户接口一般比较简单一组信号对应发送一组信号对应接收。但问题是这组接口的时序协议你得吃透不然数据送到嘴边都接不住。以我移植的这套为例发送接口大概是tx_udp_valid / tx_udp_ready 握手tx_udp_data 数据512bittx_udp_len 包长tx_udp_dest_ip / tx_udp_dest_port 目的地址端口tx_udp_src_port 源端口接收接口类似。这套时序不算复杂但有一个容易忽视的点发送时UDP协议栈可能会在任意时刻拉低ready信号自己做MAC处理或者仲裁此时你的用户逻辑必须能暂停发送也就是要有backpressure处理能力。很多人在仿真时不注意这个上板后才发现数据对不上就是因为没处理ready反压丢包了。我的做法是在用户逻辑和UDP协议栈之间加一个异步FIFO。发送方向用户逻辑写FIFO协议栈读FIFO接收方向反过来。这样用户逻辑可以和协议栈的时钟域解耦用户逻辑甚至可以用更低的时钟跑只要平均速率够100G就行。FIFO深度我选了4096×512bit实测在满速打流下不会溢出。3. 上板测试方案与实测数据解析3.1 测试拓扑与测试工具的选择UDP协议栈移植完代码综合布线都通过时序收敛这只是第一步。真正见真章的是上板测试。我的测试环境是这样的板卡AVCU118的QSFP28光口通过一根100G DAC线缆直接连接板卡B另一块VCU118或者一台100G交换机。两块FPGA都烧录同一套UDP测试逻辑但配置不同一块设为发送模式持续发射UDP数据包另一块设为接收模式统计收到的包数量、字节数、CRC错误数并实时通过串口上报。如果只有一块板卡也可以做板内回环——把TX和RX在光模块内部短接或者直接在逻辑上把发送数据回环到接收通道。这种方案适合快速验证链路和协议栈但不适合测试发射性能。测试工具方面Linux主机上有iperf3、wireshark和自定义的UDP收发程序可以灵活选择。iperf3打流测试最大吞吐wireshark抓包分析协议细节自定义程序做UDP长稳测试。不过你要注意用iperf3测100G普通电脑的PCIe网卡和CPU不一定能跑到满速你的瓶颈可能在电脑端而不在FPGA端。想测FPGA满速性能最好还是让FPGA对发然后在FPGA侧统计指标。3.2 关键测试项与通过标准光口测试不是插上光纤看灯亮就算过了。我的测试标准分几个层次第一层是PHY链路测试。主要看CMAC的link status比如tx/rx local fault、remote fault信号是否正常。用ibert工具或者CMAC内部的PRBS发生器做误码率测试常温下跑15分钟误码率低于1E-15才算合格。第二层是MAC层测试。在CMAC内部或者协议栈入口收发固定的test pattern比如递增序列或者伪随机序列目的是验证MAC转发和CRC校验没有问题。跑10分钟不报错CRC error计数为0这是硬指标。第三层是UDP协议栈功能测试。发送端发不同类型的UDP包最小包64字节、常规包1024字节、最大包9000字节即巨型帧接收端检查收到的包是否符合协议格式应用负载内容是否和发送端一致。这一层主要验证协议栈处理各种边界条件的正确性。第四层是稳定性测试。满速发流持续运行24小时以上统计丢包率。100G下丢包率必须为0性能抖动在1%以内。实际跑下来我用了ping命令测试基础连通性再用iperf3打流测带宽最后用自定义脚本做长时间满速测试。这一整套流程走完代码基本上可以放心交出去。3.3 实测数据与性能分析移植完成并调通之后我做了两轮完整测试。第一轮是调试性质的发现了一些协议栈初始化时序问题修复后进入第二轮正式测试。这里放几组核心数据第一组链路稳定性测试。两个光口对接误码率测试跑了30分钟没有出现任何bit error。CMAC的fault状态全程保持正常。第二组小包转发测试。发送64字节UDP包理论线速大约每秒2350万包。FPGA接收端统计结果连续发10亿包没有一包丢失CRC校验全对负载数据全对。这个指标说明协议栈处理小包的能力没有问题。第三组大型帧打流测试。用iperf3发9000字节UDP流实测吞吐量达到了99.2Gbps接近100G线速上限。CPU中断开销还没怎么上来说明FPGA侧的处理完全跟得上。第四组满速长稳测试。满速发流24小时总发送包数约12.5亿包接收端统计结果丢包数为0CRC错误为0FIFO溢出次数为0。这个结果还是让我很满意的。3.4 性能瓶颈在哪里说实话UDP协议栈本身跑到100G问题不大真正限制性能的往往是你自己的用户逻辑和外部接口。比如用PCIe接主机PCIe DMA的带宽往往到不了100G这就成了瓶颈。又比如用户逻辑处理每个数据包需要多次访问DDRDDR带宽不足也会拖后腿。一个很典型的案例如果用户逻辑在每个包上做复杂的状态查询比如查表、加密、压缩而查表操作延迟又高那即使UDP协议栈能满速收发用户逻辑的处理速率也远远达不到100G。这个问题在CPU上可以用多核来缓解在FPGA上就要靠深流水和并行处理来硬扛。所以如果你只是测UDP协议栈本身是可以跑满100G的。但如果你想做真实的业务比如把UDP数据包转成其他格式发送给下级设备你一定要分析清楚整条路径上每个环节能承受的带宽。很多时候系统瓶颈不在协议栈而在应用逻辑上。4. 常见问题排查与调试技巧实录4.1 上板测试最容易出的6个问题问题一链路起不来。光纤连接后CMAC一直报local fault或remote fault。排查步骤先用I2C读取光模块信息确认模块有没有被识别光功率是否正常接着检查光纤极性100G SR4有12芯光纤顺序不能错然后看CMAC配置确认线速率、FEC模式是否是双方一致最后用ibert脚本测试物理层。问题二链路起来了但ping不通。多半是MAC地址或者IP地址配置问题。FPGA侧的ARP表要能和电脑端通信确认ARP应答正常。问题三小包收发正常大包不通。检查巨型帧设置确认协议栈支持的最大包长是否大于9000字节。问题四收发都正常但iperf3打流只能跑到很低的速度。检查电脑网卡是否支持100G、PCIe速率是否够、是否开启了巨型帧和中断聚集。我遇到过几次“FPGA没问题、电脑网卡是瓶颈”的情况。问题五数据偶尔出现CRC错误。检查光模块温度是不是太高。100G模块功耗大散热不好容易误码。问题六长时间运行后FIFO溢出。接收速率突然超过处理速率多半是你的处理逻辑里有什么突发操作阻塞了接收比如DDR读写冲突、查表等待等。需要加反压信号把阻塞信息上报给协议栈让它暂停发送。4.2 Wireshark抓包与UDP协议分析技巧上板调试时用wireshark在电脑端抓包可以快速判断FPGA侧发出的UDP包是否符合预期。不过用电脑抓包有局限性电脑网卡接收能力有限抓包会造成丢包特别是100G满速时。所以调试时建议先降速比如发10%线速抓包分析完协议栈格式正确后再恢复满速。Wireshark里需要关注几个关键点UDP源端口和目的端口是否和设计一致IP头的identification字段是否连续IP头校验和以及UDP校验和是否正确注意有些FPGA协议栈默认关闭UDP校验和这在Wireshark里会显示为错误但实际通信没问题。提醒一下wireshark显示Checksum错误第一反应不是去代码里找CRC算法的bug而是确认这个包是硬件网卡校验还是软件校验很多网卡支持checksum offload软件抓到的包压根没算校验和报错是正常的。4.3 排查手段ILA、内部计数器、调试灯调试100G FPGA设计光靠看结果很难定位问题还是要在内部埋探针。我习惯在关键节点加ILA调试核但要注意ILA会占用大量BRAM而且采集速度太高可能抓不到想要的窗口。所以我的做法是只在可能出问题的路径上加ILA用计数器记录关键事件通过串口或者PCIe把计数结果定时上报。内部计数器这东西是最实用的调试手段。我在接收路径上加了几个计数器分别记录接收到的总包数、CRC错误包数、IPv4校验失败包数、UDP端口不匹配丢弃数、FIFO溢出次数。这几个数据一出来配合发送端的统计就能很快缩小问题范围。上板调试时这套计数体系帮我快速定位了一个问题UDP包长度解析在某种情况下多算了4字节导致接收端判成错误包丢弃。调试灯的用途也不能小看。板上LED可以直观显示link状态、tx active、rx active、错误状态。我在状态寄存器的某一位上拉了一个LED用来显示FIFO溢出标志长期跑测试时不需要一直盯串口关键时刻扫一眼灯就行。4.4 快速定位“上板死机”类问题的心法100G开发最恐怖的情况不是功能不对而是“上板就死机”烧录进去串口没有任何输出LED全灭看起来像是整块板卡没工作。这种情况下别急着怀疑代码按下面这个顺序排查效率高很多。第一步确认供电。100G FPGA运行时电流很大电源模块纹波大了容易出问题。拿示波器量一下FPGA内核电压确认在规格范围内。第二步确认时钟。用万用表或者示波器检查板上参考时钟有没有输出频率是否正确。CMAC的参考时钟如果没起振或者频率不对协议栈根本不可能工作。第三步确认配置电路。查看启动模式跳线是否正确。VCU118可以从QSPI Flash启动也可以从SD卡启动还可以JTAG调试。如果JTAG连不上多半是配置引脚被占用或者模式跳线错了。第四步加串口打印。在复位完成、时钟锁定、CMAC link up这些关键节点后立刻通过串口打印状态信息就能定位是哪一步卡住了。这一套流程下来90%的“死机”问题都能定位。剩下10%大概率是板子物理故障比如PCIe金手指接触不良、光口虚焊、电源模块老化等那就只能换板子了。5. 移植延展从100G UDP到更复杂方案5.1 从UDP到TCP一个不可忽视的跨度这次测试的是UDP很多人会问既然UDP都能跑100G了把TCP也移植上去不就行了这句话听起来简单实际上TCP比UDP复杂得多。UDP是无状态的不需要维护连接状态和超时重传丢包直接扔协议栈设计简单。TCP则是面向连接的要有状态机管理、序号的确认、窗口滑动、拥塞控制、重传定时器等在FPGA上实现起来体量大了几个量级。Xilinx官方的工程里其实是有TCP的但我个人建议新手先跑通UDP再碰TCP。原因有三一是TCP调试复杂网络上的问题可能是协议栈bug也可能是拥塞控制算法的问题不好归因二是TCP性能验证要求更高需要在丢包环境下测试重传率这个环境搭建起来很费劲三是大多数高带宽业务场景比如视频传输、数据采集用UDP加应用层纠错就够了未必需要TCP。5.2 多端口UDP聚合方案的实现思路100G单链路的UDP能做之后下一个自然的想法是多路的10G或者25G业务汇聚到100G链路上。这在数据中心里是很常见的需求比如多个25G服务器接入汇聚后统一走100G上行。思路其实不复杂。多个25G的UDP数据流先进各自的FIFO然后由一个仲裁器统一调度汇聚成一个100G的数据流。仲裁器的核心是公平调度和优先级控制可以用round-robin算法也可以用加权公平队列。但这里面有一个容易忽略的问题多个数据流的包之间要处理好间隔和优先级。如果两个25G流同时到达仲裁器必须让其中一个等待而这个等待会引入抖动。有时候业务能忍有时候不能忍这就要在FIFO大小和调度策略上做取舍。5.3 和PCIe DMA结合的高吞吐方案如果你的场景要求FPGA和主机之间高速交换数据那UDP协议栈必须和PCIe DMA打通。Xilinx的XDMA IP核在这块很成熟支持AXI-Stream接口可以做到和UDP协议栈直接对接。我建议的架构是PCIe DMA出数据把数据写入DDR缓存然后由用户逻辑从DDR读取并封装成UDP包通过协议栈发到网络。接收方向反过来UDP协议栈收到包后DMA引擎直接把负载搬运到主机内存。关键点在于DDR的带宽分配100G UDP线速大约需要12.5GB/s的DDR带宽这要求DDR4接口至少工作在2400MT/s以上并且留出足够的余量给读写冲突。这套方案项目里我已经实现了基本版本DMA搬运和UDP收发能跑通但还没做满速联合测试。等稳定跑完以后可以再写一篇专门的文章详细分享这里先挖个坑。6. 个人经验总结100G FPGA UDP移植看起来是个“小目标”但真做下来涉及的细节比你想象的多得多。从开源工程的代码理解到CMAC配置、时钟复位设计、收发FIFO管理再到上板测试的链路调试、性能测试、问题排查每一步都有不少坑。这篇文章把我踩过的坑和沉淀下来的方法都写出来了希望能给正在做或者准备做的朋友一些参考。我个人的体会这类项目最考验的不是代码能力而是系统性思维和耐心的排查能力。100G链路任何一环出了问题现象可能都一样链路不通或者性能上不去。你必须有一套成熟的定位方法从物理层到协议层逐层排查才能快速找到真凶。最后再多说一句别怕100G它也就是把10G的每一个细节都推到你面前而已。数据通路变宽了逻辑层面问题放大了但只要你按结构化方法一步步来100G也就是那么回事。