
上个月我拿到一块带QSFP28接口的UltraScale板卡打算把手头一套开源的100G UDP方案从参考工程里抠出来移植到自己的工程里然后上板测一下实际吞吐和丢包情况。从改RTL到最终打通打流前前后后花了一周时间中间吃了不少苦头。这篇就把移植过程中踩过的坑、验证思路和测试方法完整记录下来给准备折腾100G FPGA UDP的朋友做个参考。先说清楚一件事这里说的100G UDP不是让你在FPGA里跑一个完整的TCP/IP协议栈而是把以太网的MAC控制、GTY收发器配置、UDP/IP报文封装逻辑在FPGA内部用硬件逻辑搭起来。很多开源项目只提供MAC层和物理层UDP协议解析和生成需要你自己写或用现成的RTL模块组合。所以移植的最大工作量往往不在UDP协议本身而在“把你的板卡时钟方案、光模块接口、复位时序和开源代码的默认假设对齐”。1. 为什么要在FPGA里自己搞一套100G UDP场景与选型逻辑1.1 什么场景需要100G UDP而不是100G TCP我接触100G UDP最早是给高帧率图像采集系统做数据回传。图像传感器输出的裸数据量动辄几十Gbps甚至上百Gbps这类数据有几个特点实时性要求高、数据是流式的、允许偶尔丢几个包重传、但绝不允许因为网络拥塞导致缓冲区长时间堆积。UDP的无连接特性在这种场景下就是天然优势收发双方不需要维护连接状态发送端可以全速把数据推出去接收端按帧号或者序列号排序即可。TCP在高带宽时延积环境下要跑满带宽很麻烦拥塞控制和重传机制在FPGA里实现成本极高。硬件工程师如果自己写TCP卸载引擎工程量不比写一个完整UDP栈小多少。ROCE这类RDMA方案吞吐和延迟都很优秀但依赖网卡和交换机的配合纯FPGA环境下想用起来也比较折腾。所以很多数据采集、高频交易、科研仪器、雷达信号处理的项目最后都选了高速UDP作为传输层。另外还有一个场景是逻辑分析仪或者协议分析仪。FPGA作为前置采集设备需要把抓到的网络报文从高速口导出来UDP封装是开销最小、最容易实现的方案因为每路UDP流可以对应一批独立数据。1.2 三种路线对比商业IP、Xilinx官方IP、开源RTL我在动手之前先梳理了一下可选方案这里直接给结论方便你按自己情况选。方案成本可控性开发周期适合场景商业IP如Xilinx 100G Ethernet Subsystem高需要License或付费低很多内部逻辑是黑盒中等IP生成后主要做适配产品化、稳定优先、不介意黑盒Xilinx官方免费IP免费但受Vivado版本和器件限制中能看接口文档短但依赖IP版本快速验证、官方器件开源RTL如verilog-ethernet、Corundum免费高全部源码可见较长需要自己适配学习、科研、深度定制Xilinx的100G MAC IP其实很成熟如果你只是想在自家板卡上快速跑通100G以太网直接用IP核是最省事的。但IP核有个问题不同Vivado版本生成的IP端口会有差异换版本后要重新适配而且MAC层之下的物理层配置比如GTY参考时钟、RS-FEC开关、光模块寄存器初始化官方IP并不会替你管该踩的坑一个都不会少。开源方案的好处是你能看到每一处代码知道某个信号的含义出了问题可以打开仿真一步步跟。坏处是开源项目通常针对特定开发板比如VCU118、Alveo卡做的适配换到自己的非标准板卡所有GTY相关代码、管脚约束、参考时钟树都要重新改一遍。这次移植的主要工作量就是花在这里。1.3 我调研过的几个开源实现目前GitHub上能直接用起来的开源方案我关注比较多的是这三个方向。Alex Forencich的verilog-ethernet这是一套很全的开源以太网MAC控制器集合支持从1G到100G包含GMII/XGMII/XLGMII接口处理以及独立的UDP/IP模块。代码风格清晰结构规整适合做深入学习也可以直接拿来做100G MAC和UDP封装。Corundum这是一个开源FPGA网卡项目提供完整的PCIe DMA、队列管理和100G MAC官方支持一些Alveo和VCU118板卡。如果你想让它跑在自研板卡上得先理解它的platform框架工作量比verilog-ethernet大不少。OHWR上的一些科研项目比如CERN和各大实验室放出来的数据采集项目很多是基于100G UDP的但代码风格偏科研文档通常不多移植难度偏高。我最后选了verilog-ethernet作为MAC和UDP封装主体原因有三代码相对独立不依赖PCIe和DMA这类重逻辑模块边界清晰我只关心从光口到用户逻辑这一段之前给客户做过基于它10G版的方案踩坑经验可以直接迁移。2. 移植前必须看懂的硬件链路QSFP28、GTY收发器和时钟2.1 100G以太网到底怎么跑在FPGA上先理清100G以太网在FPGA上物理层的样子。100GBASE-R线速率是103.125Gbps通过64B/66B编码后实际有效带宽约100Gbps这与我们常说的100G近似。最常见的接口形式是QSFP28内部是4条25G通道每通道速率25.78125Gbps。也就是说FPGA里的4个GTY收发器每个跑到25.78125Gbps四条合起来就是100G。如果用的是PAM4单波长100G光模块那就需要支持PAM4的GTM收发器或DSP方案和传统4路NRZ的GTY方案完全不同。大部分FPGA开源项目都默认走4路25G NRZ路线选择板卡和光模块时一定要先确认这一点。GTY收发器内部还有CDR时钟数据恢复和均衡器。CDR的作用就是从数据比特流里恢复时钟FPGA接收侧必须对每个通道的CDR完成锁定。均衡器用来抵消高速信号经过PCB走线和连接器的损耗一般来说信号质量够就可以自动适配但如果板子上走线特别长眼图质量很差就需要手动调均衡参数。2.2 参考时钟161.1328125MHz背后的计算和板卡适配高速串行收发器的参考时钟是整个移植里最容易翻车的一环。网上搜FPGA 100G相关的问题十个有五个出在参考时钟没配对。这里说一下我的计算方式。4路25G NRZ的线速率是25.78125GbpsXilinx GTY收发器的PMA参考时钟通常取线速率除以16或除以20。25.78125Gbps除以16等于1611.328125MHz这不是一个常规晶振频率所以工程上常用161.1328125MHz这个值对应GTY协议配置里的参考时钟频率。如果板子上没有161.1328125MHz也可以提供156.25MHz然后在GTY的PLL分频参数里做补偿只是配置复杂度会上升。我手头板卡上有一颗Si53262抖动衰减器通过I2C配置后可以产生161.1328125MHz参考时钟给GTY。移植时需要在FPGA代码里加一个I2C控制器或者上电时用CPU去配置这颗芯片。有些简单板卡直接用一个可编程晶振上电输出就是固定频率这种情况下只要看原理图确认好频率就行。XDC里对参考时钟的约束也不能省。GTY的参考时钟管脚是专用的必须按原理图正确绑定否则软件会报错或者上板后CDR锁不住。2.3 用户侧AXI-Stream接口长什么样要通过100G UDP把业务逻辑的数据发出去通常用户逻辑和MAC之间通过AXI4-Stream接口对接。收发方向各一组发送是axis_tx接收是axis_rx。宽位版本一般是512bit数据加64bit的tkeep配合tvalid/tready握手。每个轴包对应一个以太网帧。这里最关键的是tuser信号不同开源项目对tuser的定义差异很大。有的项目把tuser当帧起始标志用有的用它传元数据比如报文类型、端口号有的则把tuser的第0位定义为帧头有效。移植的时候不改这个连接上去之后大概率跑不通。UDP头部处理逻辑也要规划好。如果开源项目自带UDP封装模块那么用户逻辑只需要提供目的MAC、目的IP、目的端口、源端口和有效数据。如果只拿到MAC层那就要自己生成IPv4头、UDP头并计算校验和。IPv4头部校验和和UDP校验和在硬件里实现都不复杂唯一要注意的是UDP校验和需要伪头部参与很多人第一次写会漏掉。3. 落地移植从源码目录到工程集成3.1 源码怎么挑目录怎么看从GitHub拉代码下来之后别急着往工程里塞。先看README里有没有列出支持的板卡、Vivado版本和测试方法。然后看目录结构确认哪些是RTL源文件、哪些是仿真文件、哪些是XDC约束哪些是PetaLinux或驱动代码。很多开源项目混合了软件驱动、RTL和脚本全塞进工程里会让综合时间暴涨。verilog-ethernet的目录结构比较清晰rtl下面按功能分目录有axis适配、MAC、UDP等子目录。移植时我只把rtl目录里相关的.v文件加进工程舍弃了仿真目录和参考平台目录。这一步没有太多技术含量但要根据自己的Vivado版本确认文件兼容性。老项目用的SystemVerilog语法在新版本Vivado里不一定直接兼容遇到报错就逐条看是不是语法差异。3.2 换GTY wrapper并适配你的板卡开源工程自带的GTY例化一般针对特定器件和封装比如某些IP是针对VU9P的GTH/GTY换到其它芯片或者不同bank原语的参数、位置约束都要调整。最省事的做法是直接用Vivado的Transceiver Wizard生成一个新的GTY wrapper然后替换掉开源项目里的底层例化。Transceiver Wizard生成的代码会把GTY通道配置成所需线速率、参考时钟、协议模式。需要注意的是GTY的类型GTY还是GTH还是GTM对应不同的文件线速率确认配成25.78125Gbps不要误配成25G或者10G参考时钟来源选择内部还是外部对应的CPLL/QPLL配置会变协议模板有些Wizard有以太网选项但开源RTL更多直接使用原始GTY primitive所以用Transceiver Wizard生成后还需要把用户侧接口对齐到开源模块所期望的接口。如果你不想生成IP直接改原语也是可以的。在UltraScale里GTY原语叫GTYE4_CHANNEL和GTYE4_COMMON需要手动配置的参数包括QPLL、TX/RX时钟分频、TX/RX极性等。手写原语更灵活但更容易出错我第一次就是直接手改结果上板后CDR一直锁不住后来乖乖用Wizard重生成问题就消失了。我的建议是如果你对GTY原语没有把握优先用Wizard生成不要在这上面浪费时间。3.3 XDC约束和复位时序移植过程中XDC是另一个大头。开源工程里的XDC一般包含三部分内容GTY参考时钟和收发器位置约束业务接口的逻辑管脚约束比如QSFP28的管脚、LED、复位按键时序约束包括异步时钟域的false path、跨时钟域信号的max delay。对于100G这种频率很高的设计时序约束写得不好很容易在实现阶段发现时序违例。可以像下面这样先把参考时钟约束好。create_clock -period 6.200 -name clk_si53262 [get_ports si53262_clk_p]这个6.2ns对应161.1328125MHz如果你板卡上实际带来的是156.25MHz那就是6.4ns。除了参考时钟GTY通道的输入时钟和用户AXI-Stream时钟都建议单独命名并约束。用户侧时钟频率可以参考MAC输出约195MHz左右具体以IP或开源模块生成的时钟约束为准。复位时序也要特别注意。GTY收发器的复位通常有严格的时序要求CDR锁定后不能立即送复位信号需要等待一定时间。很多开源项目把复位设计成按钮加软件可控移植时如果只是简单地把复位信号接到按键很容易出现上电后链路不稳定。我会额外加一个小型复位状态机确保上电后延迟若干微秒释放GTY复位然后等待rx_cdr_lock信号拉高后再释放MAC层复位。3.4 综合和时序收敛需要盯的事项移植完成后跑综合和实现主要看几个资源项GTY通道数量、LUT/FF使用率、BRAM/URAM使用率、以及时序报告。100G MAC加上UDP封装LUT用量一般在数万级对于VU9P这种中高端芯片来说压力不大。但如果你的目标芯片是小一号的UltraScale就要留意用户逻辑里有没有过度复杂的处理和大量的FIFO。FIFO建议用Xilinx原生IP不要自己在RTL里写一个很大深度的数组。存储开销在100G设计里很容易被低估。时序层面典型的yield问题是跨时钟域信号不加约束。GTY核心提供的rx_core_clk与用户逻辑clk是异步的UDP头解析模块如果对rx_core_clk上来的数直接打拍采样很容易出问题。移植时最好在边界加异步FIFO或打拍同步器然后XDC里用set_false_path或set_max_delay约束。不要以为所有数据在FPGA里都是同一时钟那是移植阶段最危险的错觉。4. 上板测试三板斧IBERT、内部回环和真实UDP打流4.1 IBERT确认25G物理层上板第一步永远是确认物理层不是直接跑UDP。GTY跑25.78125GbpsPCB走线、连接器、光模块任何一个环节有问题上层测出来的结果都是丢包或者完全不通。Vivado里生成IBERT IP选择对应GTY通道和参考时钟接上QSFP28光模块用Hardware Manager打开并扫描眼图。我在测试时先跑每条通道的误码率标准是可接受一段时间内的误码为零。如果误码率很高先看是不是参考时钟不对再看光模块是否识别成功最后考虑走线长度引起的信号完整性问题。眼图扫描建议用bathtub或者垂直/水平余量指标判断。100G链路余量通常要求至少达到部分检测标准但对于实验室验证只要能稳定跑一段时间的PRBS31无误码就基本可以。IBERT跑通后物理层才算是有了保障。4.2 内部回环把RTL逻辑跑通物理层没问题之后不要把电脑网卡直接接上来先用GTY的PMA回环模式验证RTL逻辑。PMA回环是GTY内部把发送数据直接回到接收端不经过外部的光模块和线缆这样可以把MAC、UDP封装、用户逻辑和GTY收发器之间的连接先跑通。具体做法是在仿真里把loopback信号拉高或者在板上通过寄存器控制。回环模式下让FPGA内部产生特定UDP报文发出去再从接收侧看有没有完整收到。如果回环能收到自己的包说明发送方向和接收方向的逻辑链路是通的。注意PMA回环绕过了光模块不代表外部链路一定没问题但至少排除了RTL这一层。更细的一步是PCS回环它会把编码后的数据回到发送侧的PCS可以验证PCS层逻辑和64B/66B编解码。如果PMA回环过了但PCS回环不过问题多半在MAC和PCS之间的接口时序上。回环模式的最上层是MAC层回环这个能过基本等于UDP协议栈的逻辑是通的。4.3 接真实网卡用iperf3/sockperf打流回环通过后才轮到连接真实网络设备打流。我用的是Mellanox ConnectX-5的100G网卡通过QSFP28光模块和短距离线缆与FPGA板卡直连。先给PC端网卡配置IP地址FPGA里也固定一个IP。FPGA侧的UDP协议栈如果支持ARP和ICMPPC端ping一下就能通如果不支持ICMP只要ARP能通就说明链路层没问题。打流工具我用的是iperf3和sockperf。iperf3适合测吞吐sockperf更适合测延迟。iperf3的UDP模式命令如下# 服务端在PC上起 iperf3 -u -s -p 5201 # 客户端也在PC上但通过网卡IP指向FPGA iperf3 -u -c 192.168.1.10 -b 80G -t 30 -l 1400这里要注意两件事。第一如果FPGA只实现了简单的UDP收发没有实现完整的协议栈回包那iperf3的“客户端”其实是把包发给FPGAFPGA可能不会回包所以测试结果界面会显示丢包。这种情况下不要慌张只要FPGA的接收计数器在涨就说明流量确实进来了。想测双向就要在FPGA侧做一个回包模块把收到的UDP包再把源和目的调换发回去。第二iperf3是单线程的在100G网卡上很难跑满一个进程大概能跑到20到40Gbps就不错了。如果想压满100Gbps建议用DPDK pktgen或者多进程同时跑我先用sockperf确认了延迟在可控范围再用DPDK pktgen把速率拉满。4.4 没有100G网卡怎么办不是每个实验室都有100G网卡。我的经验是可以用QSFP28转4xSFP28的breakout线缆把FPGA的100G口拆成4路25GPC端用一张25G网卡或者4张25G网卡分别连接。FPGA的MAC层按25G通道发送时需要把原100G逻辑的数据分发到对应通道这个工作主要是在PCS层做通道映射。如果你的开源项目不支持100G转25G的拆分还有一个办法先用FPGA内部回环验证到70分再借一台支持100G的交换机其他端口接25G网卡也能间接验证。但交换机端口的FEC配置和流量策略会影响测试结果排查起来会更费劲。所以我建议能借到100G网卡就直接借排错成本最低。5. 实测踩坑记录与优化方向5.1 QSFP28光模块识别不出来的完整排查链上板后遇到的头号问题是光模块指示灯不亮I2C读不到模块信息。这类问题的排查链路我整理成了一张表现象可能原因排查步骤模块指示灯不亮模块引脚复位、LPMode、TxDisable检查QSFP28插座的ModPrsL信号是否读到低电平I2C写失败I2C地址不对、电平不匹配确认模块I2C地址是0x50还是0x51用逻辑分析仪抓波形光信号有但链路不锁CDR未锁定、FEC不匹配查看GTY的rx_cdr_lock状态确认本端和对端FEC配置一致读到的模块型号全FI2C上拉电阻不对、插槽没识别用万用表量ModPrsL确认是光模块没有正确插入我和光模块死磕了大半天最后发现是板卡上QSFP28的复位引脚默认拉低了需要FPGA给一个高电平才能释放。开源工程默认用的QSFP28模块没有这个引脚要求所以代码里根本没处理。遇到这种问题唯一靠谱的方法是仔细看板卡原理图和光模块的规格书把所有控制引脚的状态都核对一遍。5.2 发送吞吐上不去到底是谁的锅按经验把问题分层定位速度和准确性都高很多。发送带宽上不去首先看FPGA这一侧是不是存在拉低tready的情况。MAC层在发送FIFO写满时会拉低tready但这是结果不是原因。原因是发送方向的数据通路里用户逻辑产生的数据速率达不到100G或者某个模块每拍只能处理固定字节数tvalid不能一直保持拉高。可以用Vivado的ILA抓一下axis_tx_tvalid和tready的波形如果tvalid持续为低就是上游数据源不够如果tvalid为高但tready偶尔为低就是下游MAC的FIFO在反压。再把PC端网卡看一遍用ethtool -S看发送方向的包数和字节数确认PC端的发送线程没有成为瓶颈。如果PC端确实跑不满就换DPDK pktgen它的发包速率远高于普通内核协议栈。还有一种隐蔽情况UDP包长度太短导致每个包的包头开销占比太高。100G线速率是固定的发64字节小包和发9000字节大包能达到的最大吞吐差异巨大。如果业务数据是小包可以先在FPGA里做包聚合把多个小数据拼到接近MTU的包里再发效果立竿见影。5.3 用计数器体系把丢包位置找出来丢包概率不是零问题在于怎么定位丢在哪一段。我的做法是在FPGA内部布一圈计数器从上到下每一级都留统计信号物理层通道误码计数器MAC接收帧数、CRC错误帧数FIFO写入帧数和读取帧数对比差值UDP解析层识别出来的正常UDP包数量用户逻辑实际消费的包数量。这一串计数器下来任何一级出现差值丢包位置就锁定了。测试的时候用PC发给FPGA一批有规律的数据包比如在UDP负载里放递增序列号FPGA收到后记录最后一个序列号和总包数。两边一对比就能知道丢了哪些包。这个方法简单粗暴但极其有效。注意FPGA和PC的统计口径要以包数为主不要只看字节数。UDP的包大小变化可能会掩盖某些丢包问题。PC端还可以用ethtool -S看网卡统计里的rx_missed、rx_errors等字段。如果网卡侧有errors问题在物理层或链路协商如果只有FPGA侧丢包问题在FPGA内部逻辑。两边同时抓包是最好的但Wireshark在100G下自身就会成为瓶颈需要设置合适的ring buffer大小否则抓包结果本身就是错的。5.4 后续可以从哪些方向继续做这次移植跑通之后可以扩展的方向其实很多。如果你的业务是图像采集那么下一步就是在UDP负载里加上格式和帧号信息接收端按序列号重组。如果要上控制器或者host可以在这套UDP基础上加PCIe DMA搭配DPDK在主机侧收发100G UDP就真正变成一个低延迟高速数据链路了。如果对时间同步有需求可以在MAC层加入IEEE 1588 PTP时间戳这样接收端可以把每个UDP包的到达时间记录精确到纳秒级很多科研项目都需要这个。另外如果数据可靠性要求更高可以考虑在UDP之上叠加前向纠错或者ARQ机制。注意这已经不是标准UDP而是在负载里做自定义协议但这也是FPGA方案比现成网卡灵活的地方——协议随你改逻辑都由硬件控制。从个人经验来说这次移植最大的收获是理解了一句话开源方案省的是你从零写UDP栈的时间而不是适配硬件平台的时间。RTL改起来通常一两天就能完成真正磨人的是时钟、复位、GTY参数这些“看不见的配置”。在动手之前多花一天把板卡原理图、光模块规格书、开源工程的技术文档都通读一遍比上板之后猜问题高效得多。我做过的方案越多越觉得FPGA工程里前期资料吃透的程度直接决定后面debug的时长。