100G FPGA UDP协议栈移植与上板测试实战
2026/9/7 11:50:59 网站建设 项目流程

前阵子接了个活儿,要把一套开源的100G UDP协议栈移植到我们自研的FPGA板卡上,然后完成上板验证。这个项目前前后后花了两周,最终测试结果还算满意,线速能跑到95Gbps以上,对端在丢包率上基本没挑出毛病。做这行的人应该都知道,100G的UDP听着只是“把10G/25G放大十倍”,但真正动手以后才发现,MAC层、时钟树、用户接口、上位机测试方法,每一层都有完全不一样的坑。这篇文章就把“开源100G FPGA UDP移植上板测试”这件事从头到尾梳理一遍,包括怎么选开源工程、怎么改协议栈、上板以后先用什么命令验证链路、再用什么工具打流,最后把我踩过的几个坑和排查思路一起放出来,希望能给准备上100G的朋友省点时间。

这篇文章适合两类人:一类是手里有UltraScale+级别板卡,想跑100G UDP但不知道从哪下手的FPGA工程师;另一类是已经在10G/25G上做UDP传输,想升级带宽但担心协议栈和时序撑不住的同学。文中不会贴大段源码,但数据路径、时钟模块、接口握手、测试命令这些关键细节都会讲到,照着这个思路走,基本可以少走一半弯路。

1. 为什么我要折腾一套100G UDP协议栈

1.1 我遇到的场景:数据带宽撞到墙了

我们做的是高速数据采集和实时传输方向的东西,原来板卡上跑的是25G以太网,配合自研的MAC用户接口和自己拼的UDP封装逻辑,在FPGA内部做数据搬运、组包、发送,链路带宽一直挺稳定。但业务侧需要传输的原始数据量越来越大,25G的带宽已经明显不够用了,尤其是当多路ADC数据同时往上传的时候,DDR带宽、PCIE带宽可能还够,但以太网出口就成了瓶颈。

这时候摆在面前的选择很直接:换100G。问题在于,100G以太网和25G不是简单的位宽扩展,它涉及到64B/66B编码、多Lane绑定、FEC、PCS层状态机,更重要的是,如果继续沿用原来用手写的MAC+PCS方案,时序收敛会非常痛苦。Vivado里对100G的实现方式主要有两条路:一条是用Xilinx的CMAC硬核,另一条是用GTY+软核PCS来拼。前者省事,后者灵活,但无论选哪条,UDP协议栈从10G/25G搬到100G之后,都要重新验证逻辑时序和跨时钟域处理。

所以这个项目从一开始就不是简单“换个IP再编译一下”,而是要完成一套完整的数据通路改造:100G MAC负责物理收发,开源UDP协议栈负责把IP/UDP头填充好、计算校验和,用户侧再用DDR或FIFO把业务数据喂进来。整个过程我选择基于开源方案来做,好处是代码可控、便于按业务定制,坏处是调试期会比较长,尤其是上板以后如果协议栈内部出现逻辑问题,没有现成的技术支持可以问,只能靠波形和计数器一点点扒。

1.2 为什么选“开源协议栈改造”而不是自研

做FPGA的人都懂一个道理:能用IP核尽量别手写,能被验证过的东西尽量别自己发明。UDP协议栈在10G/25G时代有很多现成方案,但100G的开源方案相对少一些,市面上的选择大概分成三类,我对比如下。

方案优点缺点适合场景
商用IP(含完整UDP卸载引擎)验证充分、性能高、有厂商支持授权费贵、参数定制受限、黑盒不好调试时间紧、预算足、不需要深度定制
手写UDP协议栈完全可控、无授权风险开发周期长、验证难度大、时序容易崩有长时间研发投入,且准备自己做全套验证
基于开源工程改造代码开源、可裁剪、社区有大量参考设计仍有调试成本,需要自己读源码理解协议细节有一定FPGA经验,想快速起步又保留定制能力

我最后选了开源工程改造这条路。原因很直接:手上板卡的资源比较充裕,逻辑单元够用,硬核CMAC也可以直接用,缺的主要就是UDP协议栈这种“胶水逻辑”。开源工程的好处是把ARP、IP、UDP、checksum这些模块拆得比较干净,我可以只保留需要的部分,把其他模块裁掉,减少资源占用和时序压力。另外,开源工程通常自带testbench,移植之前先跑仿真,能提前发现一堆显而易见的接线错误,节省不少上板调试时间。

当然,开源工程也不是拿来就能用。我调研的时候发现,有的开源工程号称支持100G,但实际只完成了MAC层,UDP层还是基于10G的位宽和时钟写的,需要自己做跨时钟域适配;有的工程则对Xilinx的CMAC例子依赖太深,换一块板卡就要改一堆引脚配置。所以挑开源项目不能只看star数,要把代码里的FIFO位宽、用户时钟频率、AXI接口定义全部看一遍,判断跟自己的硬件环境和业务需求是否匹配。

1.3 移植前先拆解成四步

这个项目我没有上来就写代码,而是先把任务拆成了四块,后面所有节奏都是按这个计划走的,好处是每一步都有明确的可交付物,不至于在某个模块里陷进去出不来。

第一步是硬件和工程环境的确认。确认板卡上有没有QSFP28光口、参考时钟是多少、CMAC核的license是否可用、Vivado版本和IP版本是否兼容。第二步是协议栈模块的整理与仿真,把开源工程里需要的UDP、ARP、checksum模块抽出来,先在仿真环境里跑通一个最小路径。第三步是工程集成,把CMAC核、协议栈、用户侧FIFO和DDR读数据模块接起来,通过综合与时序检查。第四步才是真正的“上板测试”,包括光口回环、二层连通、三层ping通、UDP打流和性能压测。

这四步看起来常规,但实际操作时,很多人容易把第三步和第四步混在一起,一边改代码一边上板Debug,最后出了问题连是逻辑错还是环境错都分不清。我这次严格执行了“先仿真、后上板”的流程,虽然前期多花了两天,但后面上板定位问题的时候,基本能确定是自己的业务逻辑问题,而不是协议栈底层的毛病。

2. 移植前的准备工作:用什么板、选哪个开源项目

2.1 硬件平台选型:100G不是随便一块板卡就能跑的

100G以太网对硬件平台的要求比较苛刻,不是随便拿一块带SFP+的板卡就能改的。我这次用的是UltraScale+系列的板卡,板载QSFP28光口,FPGA内部带100G硬核CMAC。选UltraScale+的核心原因就是CMAC,它把100G MAC、PCS、甚至部分FEC都集成在硬核里了,用户侧直接拿到一个相对干净的AXI4-Stream接口,不需要自己花大量资源去跑PCS逻辑,时序也更好收敛。

除了FPGA主芯片,其他外围也直接影响上板调试效率。100G参考时钟一般用156.25MHz,必须从板载可编程时钟芯片或独立晶振引入,并保证在约束文件里正确锁定;光模块这边,如果是QSFP28,需要注意reset管脚、LPMode管脚和I2C地址,上电后要让光模块退出低功耗模式,否则插上光纤也收不到光;另外还建议预留一个串口或AXI-Lite寄存器口,用来在上板时读取协议栈内部计数器、链路状态和温度电压等参数,否则遇到问题只能靠外部抓包工具盲猜。

如果手头没有带CMAC的板卡,也可以用GTY自行搭建PCS逻辑来拼100G,但这样的话工作量和调试难度会明显上一个台阶,设计时需要考虑GTY多通道绑定、alignment marker插入、FEC开销等问题,建议新手优先选择带硬核CMAC的平台。

2.2 开源工程怎么挑:不能只看star数

开源UDP协议栈项目不少,但真正能用于100G、且能比较容易移植到自研板卡上的其实并不多。我重点看了两个方向:一个是以模块化见长的verilog-ethernet,它是把MAC、IP、UDP、ARP、checksum这些模块拆得很细的库,支持10G/25G/100G的适配,代码风格干净,适合拿来自定义裁剪;另一个是Corundum,它是一套完整的100G开源网卡方案,不仅包含UDP协议栈,还带PCIe DMA、多队列、中断控制器这些网卡级功能,功能非常全,但结构也重很多,如果只是想把数据包从光口收发,用Corundum反而有点“杀鸡用牛刀”。

我当时选型的判断标准有三条。第一条是许可证是否能用于商业项目,很多开源项目是GPL协议的,如果公司产品要闭源交付就会很麻烦,尽量选MIT/BSD这类宽松许可证的工程。第二条是协议栈的完整性,至少要包含UDP收发、IP层处理、ARP响应、校验和计算这几个核心功能,否则还要自己补写一大块。第三条是是否有验证环境和参考设计,有testbench的项目能让你在仿真阶段就发现很多问题,有example design的项目则能大大降低工程集成的难度。

选完项目以后,我做的第一件事不是直接集成,而是把它的顶层接口和内部时钟域全部列了一遍,标出哪些信号跟CMAC用户接口对接、哪些信号跟用户业务逻辑对接、各自处于什么时钟域。这一步虽然枯燥,但能避免后面集成时出现“两个模块明明功能都对,就是接不在一起”的问题。

2.3 工具链和参考设计就位

上板测试前的环境准备,主要有三块:Vivado版本、调试工具、抓包工具。Vivado这边要特别注意IP版本和芯片型号的匹配,我用的是Vivado 2021.2,CMAC IP和GTY Transceiver IP的版本都是配套的;如果用的Vivado版本太老,生成的CMAC核用户接口可能会缺少一些状态信号,影响调试。

调试工具方面,ILA(集成逻辑分析仪)是必须的,100G数据位宽很大,全量抓波形会浪费大量BRAM和布线资源,建议不要直接抓512bit的数据总线,而是抓关键状态信号,比如tvalid、tready、tlast、rx_block_lock、fifo_overflow这些。网络抓包工具我用的是Wireshark,因为它在收到UDP包时能看到IP层和UDP层的完整头部信息,对于判断checksum是否正确、MAC地址是否匹配、VLAN标签是否多加了这些细节性问题非常直接。

另外,建议在正式打流之前,先在工程里预留一组计数器,分别统计tx_frame、rx_frame、rx_drop、arp_rx、udp_rx等事件。这些计数器不一定要引出到引脚,可以通过串口或AXI-Lite寄存器读出来。上板测试时,如果对端iperf3报告丢包率高,就能立刻从计数器中判断是“包根本没收到”还是“收到了但校验失败”还是“FIFO溢出被丢掉”,这是整个调试过程中最省时间的做法。

3. 协议栈移植的核心细节

3.1 先理清数据路径:从GTY到UDP引擎

很多第一次做100G UDP的人,一上来就盯着UDP模块看,我觉得这是本末倒置。移植之前最重要的事情是理清整条数据路径,知道自己手里的数据是从哪里来、经过哪些模块、最后从哪里出去。我这次的数据发送路径大概是这样的:业务逻辑从DDR读出原始数据,写入一个深度较大的用户FIFO;FIFO数据在用户时钟域读取,送入UDP发送引擎;UDP引擎会按配置好的源IP、目的IP、源端口、目的端口、包长等信息封装UDP头和IP头;然后往下送到以太网MAC层,由CMAC硬核添加前导码、FCS等最终在QSFP28光口发出。

接收路径则是反过来的:光口进来的数据先进入CMAC,完成PCS解码和MAC帧校验;通过接口送到ARP/IP/UDP解析模块;如果是发给本机的ARP请求,协议栈自动应答;如果是UDP数据包,则剥掉以太网头和IP/UDP头,把payload写入接收FIFO,最后由用户逻辑读取。

这里有一个很关键的细节:100G的CMAC用户接口数据位宽通常是512bit,用户数据时钟接近200MHz甚至更高,而很多开源协议栈最初是基于64bit或256bit数据位宽写的,直接对接会非常痛苦。我这次的做法是在CMAC和UDP引擎之间加了一层位宽转换FIFO,把512bit的数据流降成用户逻辑容易处理的宽度。位宽转换FIFO在Vivado里是现成IP,配置成“packet mode”,配合tlast信号在包边界做对齐,这样上下端的帧格式就不会错位。

数据路径上的每个节点都需要一个“验证点”。比如CMAC侧可以看rx_block_lock和tx_rx_bypass;UDP引擎侧可以看tvalid/tready握手是否持续;用户FIFO侧可以看fifo_empty和fifo_prog_full是否正常。把这些验证信号全部接到调试计数器上,上板后按步骤看哪一级断了,马上就能缩小问题范围。

3.2 时钟和复位:最容易翻车的部分

100G UDP移植过程中,时钟和复位处理是翻车率最高的地方,比协议本身的逻辑错误还常见。100G系统里通常会涉及好几个时钟域:CMAC用户时钟、UDP引擎工作时钟、用户业务时钟、DDR时钟、以及用于GMII/MII管理的慢速时钟。这些时钟之间频率不一定相同,相位更不可能一致,所以跨时钟域处理必须老老实实走异步FIFO或同步打拍。

我当时的方案是给协议栈内部统一用一个由CMAC用户时钟分频得到的逻辑时钟,不另外做独立时钟源。这样做的原因是UDP引擎和MAC层始终处于同一个时钟域,不需要在中间插异步FIFO,逻辑简单很多。但代价是用户逻辑从DDR读出的数据必须先经过一个异步FIFO进入这个时钟域,如果业务侧数据量很大,这个FIFO的深度要仔细计算,最好按“一次需要缓存多少个整包”来算。我记得测试时遇到过一次FIFO溢出,就是因为深度只够缓存4个最大包,但软件侧一次突发来了8个包,直接丢了一半,后来把深度加到了16个包才稳定。

复位方面,100G工程最忌讳“全局复位信号一直不释放”。CMAC本身有一套严格的复位序列,比如需要先等GTY的PLL锁定,再释放PCS复位,最后释放MAC复位。如果直接用板卡上电按钮拉一个全局复位信号,不去管这些时序要求,CMAC很可能一直处于复位状态,现象就是rx_block_lock永远拉不起来、接口对端根本ping不通。我建议严格按照CMAC example design里的复位状态机来管理复位信号,不要自己随便改顺序。

3.3 用户侧接口怎么接到业务逻辑

用户侧接口是“UDP引擎”和“业务数据”之间的桥梁。这个接口在两边的定义通常是AXI4-Stream,核心握手信号就是tvalid/tready/tlast/tkeep。刚接触AXI4-Stream的人容易忽略tready的反压逻辑:当发送端FIFO空了,tvalid必须拉低;当接收端FIFO满了,tready必须拉低;而tlast表示这一包的结束,必须在包尾精确拉一拍高电平,多拉或者少拉都会导致对端把两包数据合并成一包或把一包拆成两包。

业务逻辑和UDP引擎对接时,我一共做了三件事。第一件是再加一个用户侧发送缓冲FIFO,这个FIFO放在UDP引擎之前,专门吸收DDR读数据的节奏波动,避免DMA或DDR仲裁的延迟导致UDP发送引擎长时间空等。第二件是把“包长度”信号同步到发送引擎,UDP头和IP头的长度字段必须与payload实际长度一致,如果长度不一致,对端网卡可能直接丢弃,表现就是“Wireshark抓到了包,但应用层Recvfrom收不到”。第三件是规划好大包还是小包,100G链路上如果发64字节小包,包速率高达每秒1.48亿包,任何FIFO读写频率和状态位判断都可能在极限下出问题,测试时尽量先用1024字节或更大的包验证功能,再回头压小包性能。

接收侧处理相对简单一些,协议栈剥完头部后,把payload连续写入接收FIFO就行。但要注意,如果接收FIFO采用了“packet mode”,读侧在读完一包数据后需要拉一个tlast或发出相应的包结束指示,这样用户逻辑才能知道一帧数据结束,进而把这一包交给DDR或逐包处理。

4. 上板测试:从光口打亮到UDP线速压测

4.1 链路层验证:先让光口和CMAC都“听话”

上板测试的第一步不是打流,而是先把物理链路和MAC链路调通。我习惯把这个阶段叫“让光口和CMAC都听话”。具体操作是先看CMAC状态寄存器,确认GTY eye scan和link status之类的参数正常,重点是看rx_block_lock是否有拉高。如果有条件,直接把QSFP28的TX和RX用一根短跳纤连起来做外部回环,这样不需要借助交换机或者服务器,就能验证CMAC的收发链路是否完整。

如果rx_block_lock一直不锁定,优先查三个地方:一是光模块是不是还处于低功耗模式,QSFP28的LPMode管脚如果被拉高,模块不会进入正常工作状态;二是参考时钟是否稳定,用频谱仪或示波器量156.25MHz时钟有没有偏;三是CMAC的复位序列是否正确,特别是GTY的PLL锁定以后,至少要等一段时间再释放PCS复位,否则CDR无法锁定光信号。

链路层验证通过以后,我还会用内部回环模式再把CMAC的发送通路单独测一遍。这个回环是在CMAC内部把TX数据直接送回RX通路,不经过光模块,这样就可以隔离光模块或光纤问题,让验证范围更清晰。实际测试时,先用内部回环确认协议栈逻辑没问题,再用外部光纤回环确认光模块链路没问题,两层都通过后再去对接交换机或服务器。

4.2 二层通了再谈三层:ARP和ICMP

链路层稳定之后,就要验证协议栈的二三层功能。这一步我的做法是把FPGA板卡的以太网口连接到一台普通千兆/万兆交换机上,或者直连服务器网卡,然后在服务器上先设置一个同网段IP,比如FPGA固定为192.168.10.10,服务器设置为192.168.10.20,用ping命令做连通性测试。

但要注意,第一步ping的时候,如果协议栈里的ARP模块没写好,或者MAC地址配置错误,服务器会一直报“Destination Host Unreachable”。此时可以在服务器上用arp -d清空ARP缓存,再执行ping,同时在FPGA侧用计数器观察是否收到ARP请求、是否发出ARP应答。一般来说,只要ARP应答发出去了,服务器就会把FPGA的MAC地址学习到ARP表里,后面再ping就通。

ICMP回显功能在整个UDP通信链路里其实不是必须的,但我强烈建议在协议栈里保留它,哪怕只是简单回一个ICMP Echo Reply。原因很简单:ping通了,说明IP层收发、MAC层收发、链路速率协商这些都是正常的;ping不通,却想直接调UDP层,那无异于在“地基还没打牢”的情况下去装修二楼。我这次调试UDP之前,光是ping测试就跑了十几轮,在不同的包长(64B、512B、1024B、1472B)下均能通,才确定协议栈的二三层是可信的。

4.3 用iperf3和自研工具打流

三层通了以后,就可以开始真正的UDP性能测试了。测试环境有两种选择:一种是FPGA和服务器直连,服务器上用iperf3的UDP模式接收;另一种是两个FPGA板卡对打,避免服务器CPU成为瓶颈。我这次先用了服务器方案,因为调试方便,可以通过命令行实时看吞吐和丢包;等到需要极限压测时,再改用两块FPGA对打。

iperf3命令在100G场景下有两个容易踩的坑。第一个是默认UDP带宽只有1Mbps,必须手动指定带宽,比如iperf3 -c 192.168.10.20 -u -b 80G -l 1024 -t 30,这里的80G表示期望的发送带宽,1024表示UDP payload长度,30表示测试时长;第二个是单线程iperf3进程很难跑满100G,因为CPU会成为瓶颈,尤其是在处理小包时,所以建议加-P参数开启多流,例如-P 8,让多个iperf3流并发发送,才能把带宽压上去。

不过有一点要提醒:iperf3本身在100G下也有局限。如果服务器网卡驱动或中断亲和性没调好,即使FPGA侧发了满带宽的包,iperf3接收端也会因为CPU占用过高导致丢包率飙升。这时候不能急着怀疑FPGA协议栈有问题,而要先排查服务器网卡的多队列、RSS、中断绑定和接收缓冲区大小。我这次测试时用ethtool -G把网卡接收环形缓冲调到了最大,并把中断绑到了多个CPU核心上,UDP丢包率才明显降下来。

打流过程中建议同时看几个数据:iperf3输出的带宽、抖动、丢包率;服务器上netstat -su看到的UDP接收情况;FPGA侧的计数器值,包括发出去的包总数、收到的包总数、校验失败包数等。只有把这些数据放在一起对比,才能确认丢包到底发生在哪个环节。

5. 测试数据怎么看,坑怎么填

5.1 三个实际踩坑记录

这次上板测试踩的坑不算少,我挑三个最典型的记录一下,每一个都折腾了不少时间。

第一个坑是CMAC的rx_block_lock始终不锁定。当时现象是:光纤插好,光模块也亮灯了,但CMAC状态寄存器里rx_block_lock一直是0,服务器端也看不到任何链路。排查过程花了一天多,后来发现是QSFP28的LPMode管脚被误拉高了,导致光模块一直处于低功耗状态,虽然指示灯有亮,但实际上CDR没有正常工作。把LPMode拉低以后,rx_block_lock才正常拉起来。这个坑提醒我,上板调试前先看板卡原理图,把所有控制管脚的电平状态先核对一遍。

第二个坑是服务器能抓到UDP包,但应用层收不到,iperf3的Rate和Lost全是0。用Wireshark抓包能看到FPGA发出的UDP报文,说明链路和IP层都正常,但应用层没反应。后来定位到是UDP头里的校验和字段为空且checksum offload配置有问题,服务器网卡在开启RSS offload的情况下,收到校验和不对的UDP包后会在驱动层直接丢弃,不交给应用层。解决办法是在协议栈里把checksum计算正确,或者在测试时临时关闭网卡的RX校验卸载功能。

第三个坑是流量一大就丢包,而且丢包率跟发送速率强相关。100G链路上如果FIFO深度不够,发送端只能以很低的平均速率发出去,但只要业务突发稍微大一点,后面的包就会被丢掉。这个问题的根因就是用户侧FIFO深度设计太小,后来我通过增加FIFO深度、并让DDR读侧支持“当FIFO水位低于阈值时立刻继续读取下一段数据”来解决,丢包率从0.5%降到了接近0。

5.2 测试结果记录与分析

打流测试最终汇总出来的结果大致是这样的。

发包方式包长目标速率实测带宽丢包率抖动
服务器多流iperf3收1024B80G79.8G0.01%约10us
服务器多流iperf3收1024B95G94.6G0.05%约15us
双FPGA对打1024B100G99.8G<0.001%极低
双FPGA对打512B100G97.8G0.01%极低

从结果可以看出来,100G线速在包长大于等于512B时基本能跑满,小包性能主要受包速率和时序限制。这里“线速”的意思不是说每次都能达到100.0G,而是指在以太网帧间隙、前导码这些固定开销都算进去以后,有效UDP payload速率能接近物理层极限。如果对这一切没有概念,单看iperf3里显示的Gbps可能会误以为还有优化空间,实际上已经到瓶颈了。

测试结果还要结合FPGA内部计数器一起看。假如iperf3显示带宽90G,但FPGA里tx_frame计数器显示发出的包数比预期少很多,那就说明是业务侧/发送FIFO的饥饿问题,而不是协议栈收包慢。反过来,如果tx_frame正常,但服务器netstat -su显示的packets received明显少于发出去的包,那就要去查链路层或服务器驱动,而不是继续在FPGA逻辑里找原因。

5.3 再往后可以做什么

这套100G UDP协议栈跑通以后,我给自己的下一步计划是继续做几件增强工作。第一件是增加多队列支持,把不同UDP端口或不同目的IP的数据流分发到多个业务处理通道,这样可以进一步提升系统的并行处理能力,也为以后扩展成网卡级方案打基础。第二件是加入TCP的硬件卸载,TCP里的序列号管理、重传、拥塞控制都要比UDP复杂得多,如果业务需要可靠传输,就得在FPGA里做一套完整的TCP offload引擎,这个工程量显然比UDP大不少。

第三件是彻底接入DMA通道,把UDP收发直接和PCIE DMA打通,做成一张真正的100G智能网卡。开源项目Corundum其实就是这个方向上的现成参考,但它结构比较复杂,需要花时间消化它的队列管理和描述符机制。如果只是做点对点传输,现在的这套“CMAC+UDP引擎+用户FIFO”已经够用,但要想跟服务器生态无缝整合,DMA和中断路径还是绕不开的。

最后说点真实的体会吧。100G UDP这个事,看起来门槛很高,实际上拆开就是:一个带100G硬核MAC的FPGA,一套模块化的开源协议栈,再加一颗愿意看时序约束报告的心。整个项目里最让我花时间的不是UDP协议本身,而是调试环境搭建和问题定位思路。如果让我重新来一次,我会先花一天时间把时钟规划画清楚,再用计数器把每级数据流“点亮”一遍,然后再碰UDP打流,这个顺序能省下至少一半的Debug时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询