news 2026/9/7 6:36:00

FPGA上实现100G UDP传输:开源协议栈移植与上板测试全记录

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FPGA上实现100G UDP传输:开源协议栈移植与上板测试全记录

做任何高速接口项目,第一步永远不是写代码,而是先把数据通路画出来。100G UDP这个项目,我去年在一个数据采集卡上完整跑过一遍,从拿到开源代码到上板打流通过,前后大概折腾了三周半。这篇就把移植和上板测试过程中最关键的环节、最容易踩的坑,以及最终实测的数据整理出来,给正准备做类似项目的朋友一个参考。

1. 整体设计与方案选型:为什么用开源UDP而不是商业IP

1.1 项目目标和应用场景

这个项目的本质,是在FPGA上实现一个单端口100Gbps的UDP数据传输链路。听起来就是把协议栈跑起来,但实际牵涉到的东西远不止UDP那几个字段。100G UDP在真实项目里的用途非常明确:高速数据采集、网络流量回放、数据中心内部节点间的大块数据搬移。因为UDP无连接、头部开销小,做纯数据搬运时比TCP友好得多,尤其适合FPGA这种擅长流式处理、不适合做复杂状态机的硬件。

我当时的场景是:前端ADC持续产生高速数据流,经过FPGA打包成UDP报文,从QSFP28光口发出去。接收端是服务器上的100G网卡。数据的实时性要求高,允许少量丢包但不能出现链路中断,时延要低。

1.2 开源协议栈的选型对比

市面上能做100G UDP的FPGA开源方案,主流就那几个。我用过的有Alex Forencich的verilog-ethernet、Corundum,还有OpenCores上的一些老古董。最终选了verilog-ethernet,原因很直接:它对AXI-Stream接口的封装非常干净,MAC、IP、UDP、ARP各层模块解耦,代码风格清晰,而且作者长期维护,对Xilinx和Intel两家器件都有适配。

Corundum的优势在于它是一套完整的高性能网卡方案,包含了PCIe DMA、多队列、卸载引擎,如果你要做的是一个真正的100G智能网卡,Corundum是更好的起点。但如果只是需要一个轻量级的UDP数据通道,Corundum的复杂度反而会成为移植负担。老古董那些OpenCores项目,大多数停在10G/25G级别,100G的支持基本是残缺的。

1.3 为什么“移植”这件事不是复制粘贴

很多第一次做的人会以为开源代码拿来就能跑,实际上完全不是这么回事。verilog-ethernet的demo工程是针对特定板卡和特定FPGA型号的,你拿到的是一堆通用RTL,要做到自己的板子上,至少需要改这几类东西:

  • 时钟方案:100G以太网的线速率是固定的,但用户侧逻辑时钟可以根据位宽设计选择不同频率
  • SerDes收发器配置:Xilinx的GTY/GTH、Intel的E-tile,底层都不一样
  • 复位与初始化时序:每个平台的复位要求不同,尤其在高速收发器上
  • 与厂商IP的对接:比如Xilinx CMAC(硬核100G MAC)的使用方式

所以这个项目的核心工作,一半在理解协议栈逻辑,一半在硬件适配。

2. 时钟、复位与位宽转换:移植中最容易翻车的三个基础点

2.1 100G MAC的时钟域设计

100G以太网在FPGA内部的数据位宽和时钟频率是有讲究的。标准100GbE的PCS层在64B/66B编码之后,线路速率是103.125Gbps。如果MAC层对外数据接口采用1024bit位宽,那么用户侧时钟就是103.125G / 66 × 64 / 1024,约等于97.7MHz,这个频率太低了,逻辑好跑,但很多外部接口模块(比如PCIe DMA、高速ADC接口)不喜欢这么低的频率。实际工程中常见的做法是用512bit @ 195.3MHz,或者322.265625MHz @ 320bit。

verilog-ethernet里的eth_mac_100g模块,数据接口就是独立的AXI-Stream,位宽可参数化。官方demo用的是1024bit位宽,配大概97.7MHz或者156.25MHz的时钟(取决于是否启用FEC)。我在移植时,为了和DMA模块的时钟域对齐,用了512bit @ 195.3MHz。

这里有个容易绕进去的点:PCS层的时钟和MAC用户侧时钟不是一个东西。CMAC硬核内部有自己的时钟方案,你外部需要做的是在CMAC的用户侧接口和你自己的逻辑之间保证时序收敛。如果硬核支持内部时钟分频,可以省不少事。

2.2 复位时序的坑

这是移植过程中让人掉头发最多的环节。Xilinx的CMAC参考设计里,复位信号的处理非常讲究:GTY收发器需要先完成复位,然后PCS的RX侧还需要等待复位完成信号,最后才能释放MAC层复位。如果你一股脑把所有模块的复位信号接到同一个全局复位上,大概率出现的问题是——链路能起来,但偶尔不工作,或者收发时钟恢复不稳定。

我当时的做法是,按照官方example design里的复位时序,做一个简单的复位状态机:上电后先复位GTY的TX和RX,等待txresetdone和rxresetdone拉高,然后释放CMAC的复位,再等待cmac的rx_axis_reset_done信号。用户侧逻辑的复位放在最后。

2.3 AXI-Stream位宽变换和tkeep处理

开源协议栈的UDP接口通常有一个参数化的数据宽度,但用户业务逻辑的数据粒度不一定匹配。比如我这边ADC数据是256bit位宽,而UDP发送模块是512bit,中间必须加异步FIFO做位宽转换和时钟域转换。

这里有个细节,AXI-Stream的tkeep信号在处理非整帧对齐的数据时特别容易出错。以太网帧最小64字节,最大1518字节(巨型帧可以到9K),你打包数据时最后一个beat的tkeep必须精确对应有效字节数,否则对端网卡会丢弃报文。我在调试时遇到过一种情况:帧能发出去,Wireshark也能抓到,但接收端的socket收不到,排查了半天,最后发现是tkeep在最后一拍没有正确拉低,导致对端校验字节长度时出错。

注意:AXI-Stream的tlast信号必须与最后一拍数据对齐,而tkeep在最后一拍必须表示出真实有效的字节数。很多开源代码在数据宽度是8的整数倍时会默认tkeep全1,如果你的业务数据不是正好填满最后一拍,这个默认值就要改。

3. 开源UDP协议栈的修改与适配过程

3.1 协议栈模块结构梳理

在使用verilog-ethernet之前,我先把它的目录结构完整走查了一遍。这个项目核心模块分成几层:

  • 数据链路层:eth_mac_10g/25g/40g/100g,负责MAC帧的收发、FCS校验、 preamble处理
  • 网络层:ip_arbiter、ip_in、ip_out、ip_arp,负责IP包的路由和ARP缓存更新
  • 传输层:udp_64、udp_1024、udp_axis_tx/rx,负责UDP头部解析和校验和计算
  • 公共模块:axis_fifo、axis_async_fifo、lfsr等

移植时不需要从头读每一个模块的每一行代码,但一定要把数据流方向搞清楚。我的顺序是:先从udp_axis_tx和udp_axis_rx看起,理解用户数据如何被封成UDP/IP帧,再回头去看MAC侧的信号接口。

3.2 修改MAC与PCS对接层

verilog-ethernet自带的MAC模块是纯RTL的,但如果你的FPGA有硬核MAC(UltraScale+的CMAC),直接用硬核是更稳的选择。硬核的资源占用低、时序好、支持FEC和链路训练。问题在于硬核的接口时序和开源MAC模块不一样,你需要写一个桥接层,把开源代码里的MAC接口信号映射到CMAC的AXI-Stream接口上。

这个桥接层看起来只是信号重命名,实际上有两个难点:一是CMAC的axis接口有严格的复位时序要求,二是CMAC的tuser信号中包含错误标记,接收方向要处理error、bad_fcs等状态,而开源代码在某些版本里忽略了tuser,导致坏帧没有被过滤。

我当时写了一个接收方向的过滤逻辑:当tuser标记该帧CRC错误时,直接丢弃整帧,并且不回传用户数据。这个过滤逻辑大约二十行,但价值非常大,因为100G链路上偶尔出现的错误帧如果直接进入UDP层,会造成上层业务解析错误。

3.3 ARP和ICMP的处理

如果你希望服务器ping通FPGA板卡,ARP和ICMP是必须的。verilog-ethernet里有完整的arp_cache和icmp模块,但默认配置下ARP缓存表深度是有限制的,如果你要长时间大流量通信,ARP表项老化或者溢出会导致链路中断。

我实际遇到的情况是:用iperf3打流时,前几分钟一切正常,跑一段时间后突然断流,重新ping又通。排查了很久,发现是ARP缓存表老化刷新机制和业务流量的交互出了问题。解决办法是把arp_cache的缓存时间调长,同时设置了静态ARP表项。

经验:如果通信对象是固定的几台服务器,强烈建议在FPGA内部做静态ARP映射,不要在业务运行时依赖动态ARP刷新。动态ARP一旦缓存失效,会产生大量广播请求,在100G链路上影响非常明显。

4. 上板测试全流程实录

4.1 测试环境与工具链

硬件环境这样搭的:

  • FPGA板卡:UltraScale+ VU9P,板载QSFP28接口
  • 光模块:100G QSFP28 SR4,配多模光纤
  • 服务器:双路Intel Xeon,操作系统Ubuntu 20.04
  • 网卡:Mellanox ConnectX-5双口100G

软件工具用得最多的是iperf3、scapy和tcpdump。iperf3测带宽和丢包率,scapy用来构造特定格式的UDP报文测试协议栈对异常包的响应,tcpdump在服务器侧抓包验证FPGA发送的报文格式是否正确。

4.2 底层检测:IBERT与链路协商

正式跑UDP之前,必须先确认物理层没问题。这一步很多人会跳过,但我觉得是最省时间的步骤。用Vivado的IBERT IP做一次误码测试,让GTY收发器工作在回环模式,连续跑几分钟,如果误码率不是0,说明PCB布线、电源或者光模块有问题,这时候去调上层代码没有意义。

我的实测结果是,在VU9P上GTY跑103.125Gbps线速率,IBERT误码率为0,眼图裕量在合理范围内。确认无误之后,才把CMAC配置进去,看link状态是否能拉高。第一次上电时,link状态一直在down和up之间跳变,后来发现是光模块的信号完整性参数没有匹配,调整了TX的预加重设置后稳定下来。

4.3 MAC层测试:从loopback到真实链路

链路稳定后,先用板载回环测试MAC层收发通路。有些板卡支持外部光纤环回,把光模块的TX和RX短接,这样验证的是完整的SerDes和光模块通路。回环测试能通之后,再连接服务器网卡。

连接真实链路时第一次ping不通,排查步骤是:

  • 在FPGA侧加一个ILA,抓CMAC接收方向的信号,看是否有preamble和SFD
  • 在服务器侧用ethtool看link状态,确认物理层正常
  • 在服务器上tcpdump抓包,确认ARP请求是否到达

最后的结论很简单:FPGA侧的ARP响应模块没有使能,ICMP的echo响应也没有打开。开源代码里这两个模块默认有,但我在前面“精简工程”的时候误删了一个例化。这也算是个教训——移植时删代码要谨慎,尤其是网络层以下的功能模块。

4.4 UDP端到端打流与性能测试

链路和IP层都正常后,就进入最核心的环节:性能测试。

先测试的是FPGA发送、服务器接收方向。在FPGA内部生成了一个固定载荷的UDP流,用计数器模拟业务数据,打包成标准的UDP报文连续发送。服务器侧用iperf3的UDP模式收流:

iperf3 -s -u -i 1

FPGA侧的发送模块跑起来后,iperf3显示的接收速率稳定在99.2Gbps左右,丢包率在1e-7以下。这个结果说明协议栈本身没有成为瓶颈。

然后是服务器发送、FPGA接收方向。服务器侧用iperf3打流到FPGA的IP地址,FPGA内部做了一个简单的回环计数,把收到的UDP报文数量统计后再发回来。这一步主要验证的是FPGA接收路径的吞吐能力。

实测下来,接收方向同样能跑到接近100Gbps。但这个环节出了一个有意思的问题:当UDPrx侧的业务FIFO发生背压时,由于上游IP模块没有流控机制,FIFO溢出导致丢包。这其实是开源协议栈的一个特点——内部没有端到端的流控,如果你的业务消费速率跟不上接收速率,丢包是必然的。

4.5 小包性能与大包性能的差异

吞吐测试还有一个值得记录的指标:不同报文长度下的速率。以太网物理层速率固定100Gbps,但帧数(pps)是有限制的。64字节小包的最高速率大约是1.4881Mpps,数据有效载荷只有约6.67Gbps;而1518字节大包的有效吞吐可以到99Gbps以上。

我在测试时专门对比了128字节、512字节、1024字节和1518字节四种帧长,结果如下:

帧长(字节)理论最大有效载荷(Gbps)实测有效吞吐(Gbps)丢包率
128约7.877.82< 1e-6
512约31.531.4< 1e-6
1024约63.162.90
1518约98.798.30

这个结果符合预期,也说明了一个现实:如果你的业务想跑满100G,尽量用大包。设计的UDP包长如果在128字节以内,再好的协议栈跑出来也就七八个G,没有意义。

5. 常见问题与调试经验记录

5.1 链路不稳定,link状态反复跳变

反复跳变最常见的原因是物理层的信号完整性或GTY配置问题。除了IBERT验证之外,还要检查光模块的FEC配置。100G SR4光模块在某些板卡上需要打开RS-FEC才能稳定工作,否则误码率超标会导致链路反复重启。我在测试中开了FEC之后,误码率直接降了几个数量级,链路状态也稳定了。

提示:FEC开与不开直接影响链路的稳定性,但会增加约数个纳秒的时延。对时延敏感的场景需要在稳定性和时延之间做权衡。

5.2 能ping通但UDP业务不通

这种问题通常是ARP或UDP端口配置导致的。ARP缓存表项老化后重新发起ARP请求,如果请求是广播帧,在100G链路上占用带宽很小,不会造成断流。但如果板卡上有防火墙性质的分包过滤逻辑,需要确认UDP目标端口号是否在过滤白名单里。还有一种隐蔽情况:板卡的UDP协议栈只处理目标MAC是本机MAC的帧,如果对端用不同VLAN ID发送,MAC地址可能不匹配,导致帧被丢弃。

5.3 丢包率过高

丢包率高的排查顺序,我总结为:先看FIFO深度,再看背压机制,最后看校验和。

业务侧FIFO深度不足是最常见的原因。UDP接收模块瞬间收到大块数据时,业务逻辑来不及消费,FIFO溢出就会丢包。解决办法一个是加大FIFO,更深层的方案是让业务消费逻辑能够随机应变地调整消费速率。

另一个容易忽视的点是UDP校验和。部分软件工具发送UDP包时校验和为0(表示不校验),但如果FPGA侧的校验和检查模块强制将校验和0当作错误处理,所有这样的包都会被丢弃。很多开源代码里这种边界情况处理得不够好,需要自己修改。

5.4 逻辑利用率过高导致时序不过

当你把整个协议栈加进一个大工程时,资源占用可能并不高,但时序收敛会很痛苦。原因在于MAC层512bit的数据通路跨了太多组合逻辑。我的经验是:模块之间全部插AXI-Stream寄存器级(也就是SRL/FF),顶层做pipeline,不要省那几级寄存器。在100G这种速率下,把时序约束做死在第一位,逻辑简洁性是第二位。

6. 一点个人经验总结

整个项目做完后,最大的体会是:100G UDP的代码本身并不复杂,复杂的是它和硬件平台之间千丝万缕的耦合关系。开源的协议栈给了你一条捷径,但理解每一层为什么这样设计,才能做好移植和调优。

另外一个建议是测试一定不能省。IBERT、回环、ping、小包打流、大包打流,每一步都有它存在的意义,跳过去直接跑业务,出了问题再回头查,时间成本翻倍。

最后再分享一个小技巧:在板卡上留一组计数器,统计发送帧数、接收帧数、丢弃帧数,通过串口或者DDR刷新出来。这一组计数器的价值比任何逻辑分析仪都大,因为你不用暂停系统就能实时看到数据通路的健康状态。可以说,调ぜん了它,这个项目就算真正收工了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 6:35:01

用Cocos Creator 3.8制作3D合成大西瓜:物理引擎与TypeScript实战

这次我们来看一个非常典型的小游戏项目&#xff1a;用Cocos Creator 3.8制作 3D 版合成大西瓜。玩法大家都很熟&#xff0c;点击或触摸投放水果&#xff0c;相同等级的水果碰到一起就合并成下一等级&#xff0c;一路合成到“大西瓜”。区别在于这次不是 2D 平面逻辑&#xff0c…

作者头像 李华
网站建设 2026/9/7 6:34:49

MCU芯片开发实战指南:从启动流程到电源设计的全面解读

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 6:33:24

深度网格投影与360立体渲染:UE5 VR性能与分发实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 6:32:52

首屏优化:从拆包到系统博弈,构建资源加载与渲染可观测性闭环

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 6:32:39

workbuddy实战:从AI智能体到浏览器自动化的完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 6:32:15

21个深度学习项目实战:从环境配置到Transformer的进阶路线

简介&#xff1a;面向机器学习初学者与中级开发者&#xff0c;这套深度学习实例包以21个可运行项目为主线&#xff0c;由浅入深覆盖深度神经网络、卷积神经网络、循环神经网络、长短时记忆网络、自编码器及生成对抗网络等核心结构&#xff0c;并延伸到图像分类、手写数字识别、…

作者头像 李华