news 2026/9/9 9:28:41

从10G到100G:FPGA UDP offload移植实战与调试记录

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从10G到100G:FPGA UDP offload移植实战与调试记录

之前一直在10G的XGMAC上写UDP offload,今年项目要求吞吐直接上100G,我第一反应是:这有什么难的,找一个开源的100G UDP核,改改接口就上板。等真做完一整轮移植加测试,我必须承认这个想法过于乐观。100G以太网和10G以太网虽然都叫以太网,但从CMAC位宽、小包线速到DMA描述符深度,几乎没有一层能沿用原来的假设。这篇文章把整个移植和上板调试过程完整记录下来——怎么选型、改了哪些东西、测试怎么搭、以及三个让我改了多个版本的坑,希望对正在从10G/25G往100G迁移、或者刚开始接触FPGA 100G UDP的同学有帮助。

1. 100G UDP移植到底在移什么:从10G到100G不是改参数

1.1 名义差异之外的三个数字差异

很多人觉得100G只是把数据位宽变宽、时钟变高,其实背后的设计压力完全不同。看一组我在设计时反复算过的数字:

项目10G以太网25G以太网100G以太网
常见MAC用户接口64bit @ 156.25MHz64/128bit @ 312.5MHz512bit @ 322.265625MHz
线速小包(64B)速率14.88 Mpps37.2 Mpps148.8 Mpps
常见光模块形态SFP+SFP28QSFP28 / QSFP-DD + RS-FEC
典型PHY编码64B/66B64B/66B64B/66B + RS-FEC(544,514)

如果用户逻辑每拍处理一个以太网包,10G时代157MHz时钟处理14.88Mpps还比较轻松,25G时代312MHz处理37.2Mpps也勉强能跑。到100G,322MHz时钟对应148.8Mpps,每个包的时钟预算只有2.17拍;更要命的是,512bit宽的数据接口每一拍最多能装下8个64B小包。用户逻辑如果还维持“一拍处理一个完整包”的思路,虽然名义上算力有余,但描述符分配、校验和计算、包边界记录这些周边操作会迅速变成新的瓶颈。

1.2 小包线速的乘法效应

100G线速小包速率148.8Mpps这个数字,是整个移植过程中反复出现的“噩梦常数”。它不仅约束FPGA侧包处理逻辑,还一路传导到DMA描述符的提交速率、PCIe中断的触发频率、Host侧网卡驱动的事件吞吐能力。10G时候一个策略在14.88Mpps下没问题,到了100G完全不适用,因为每个子系统的压力都放大了十倍。

1.3 所以“移植”这个词应该拆成四件事

做完一轮之后,我把“把10G UDP移植到100G”这件事拆成了四个子任务:时钟与位宽适配、包处理节奏重调度、DMA描述符与中断深度重配、Host侧驱动参数重调。后面所有的排查和调优,基本都围绕这四件事展开。

2. 开源核怎么选:社区方案与自研的边界

2.1 我调研时的三个候选

开源生态里100G UDP相关的东西不算多,但也不是空白。当时我重点看了三个方向:

  • XUP Vitis Networking:Xilinx官方的网络参考设计,包含CMAC + UDP offload + XDMA/QDMA的完整链路,FPGA侧代码和Host侧驱动都有示例,社区活跃度高。
  • Verilog-Ethernet:Alex Forencich维护的经典以太网开源栈,模块划分清晰,10G/25G上很成熟,但100G需要自己拼装多个模块,没有现成的完整工程。
  • 自研mini UDP core:只做CMAC原语集成 + UDP解析/组包 + 简单DMA搬移,工作量可控但需要从零解决一堆坑。

2.2 选型逻辑:能用现成的就别从零造

项目时间紧张,最终选择了XUP Vitis Networking的100G UDP参考设计作为基线。理由很直接:CMAC IP的集成、RS-FEC配置、XDMA中断链路这些基础设施都是现成的,省掉了我最不擅长的部分。而UDP业务相关模块,比如校验和计算、ARP表、ICMP回应逻辑,我用自己的实现替换掉原来的example代码。

选型时候最容易踩的坑是看README说支持100G就以为开箱即用。实际代码里可能默认example是针对10G/25G编译的,DMA也可能是AXI-Stream而不是Memory-Mapped,Host侧驱动没有对应的DPDK PMD,甚至license不允许商用。这些都要在动手移植前确认清楚。

2.3 三个候选的横向对比

维度XUP Vitis NetworkingVerilog-Ethernet自研mini UDP
原生100G支持CMAC + RS-FEC完整流程需要自己组合需要完全自建
DMA子系统XDMA/QDMA,可配置无,需外接可裁可简
Host侧软件生态DPDK/testpmd示例需自研驱动
集成难度中等,example多
文档活跃度高(但100G资料少)完全看自己

三个方案里我最后选了第一个,核心原因是它把“能上板跑通”的概率拉到了最高,剩下要花时间的只是把业务逻辑改顺手。

3. 移植落地清单:位宽、时钟、DMA描述符一个都不能漏

3.1 用户侧AXI-Stream宽度与跨时钟FIFO

CMAC的用户接口是512bit @ 322.265625MHz,可以配置,这个频率和位宽组合是线速下带协议开销的必然结果。我们原来的用户逻辑深度绑定在128bit总线上,第一步就是把所有包处理模块改成512bit接口,并在每个时钟域边界加上异步FIFO。

跨时钟FIFO的深度不能拍脑袋。100G线速下,假设最坏情况每微秒有12.5KB数据涌入,异步FIFO至少得容纳半微秒的突发,也就是6.25KB。我用的FIFO深度是512bit x 1024,也就是64KB,在WRITE_LEVEL超过一半时拉高almost_full,给下游留出反应时间。这个设计后来在打流测试中几乎没有出现因FIFO深度不足导致的丢包。

3.2 CMAC初始化顺序:别一上电就等下不了link

CMAC初始化有个严格顺序,尤其是RS-FEC使能的100G链路。大致流程是:

  1. 释放GT复位,等待tx_reset_done和rx_reset_done拉高。
  2. 等待CMAC内部时钟稳定,读cmac_status寄存器确认tx_clk_stable和rx_clk_stable。
  3. RS-FEC使能时,等待rx_aligned、rx_rsfec_lock等状态位置位。
  4. 最后才把AXI-Stream接口的复位释放,开始收发数据。

我一开始图省事,把CMAC恢复位和用户逻辑复位一起释放,结果出现概率性的link up慢和RX alignment超时。后来改成严格分域复位,问题消失。上板调试时最值得做的第一件事就是把CMAC状态寄存器的每一个bit都映射到可读寄存器,link不up时直接读状态,而不是抓波形。

3.3 tkeep/tuser处理:512bit接口上SOP和EOP可以同拍出现多次

512bit接口碰到64B小包时,一拍里最多有8个完整包,这意味着一拍内可能有多个SOP和多个EOP。这是一个很多人移植时忽略的点。10G时代64bit接口一般一包跨两拍,SOP和EOP不会挤在同一拍;100G必须处理“一拍多包”的情况。

我写的包解析逻辑不再用一个单包状态机,而是分两步:先用组合逻辑根据tkeep扫描出当前拍内所有SOP/EOP边界,生成一个“包边界位图”;然后再根据位图并行地为每个包分配描述符或做校验和计算。这一步是后续能打满线速的基础。

3.4 UDP首部处理与校验和分工

UDP/IPv4校验和最好在FPGA侧硬件计算。IPv4首部校验和用16bit one‘s complement sum很简单,UDP校验和如果不需要可以关掉,但要注意Host侧驱动如果开启了RX校验和验证,需要确认两者分工不会造成双重计算。我建议FPGA侧计算并填好IPv4首部校验和,UDP校验和默认填0;如果对端也是自研逻辑,两边约定好即可。实测这样处理对性能影响最小。

3.5 DMA描述符与中断:10G的参数直接带过来一定出事

这是移植过程中改动最隐蔽也最影响结果的一环:

参数10G习惯值100G建议值说明
RX描述符环深度2562048或4096小包线速下描述符消耗极快
TX描述符环深度2561024发送侧相对宽松
中断聚合关闭或很少建议使能避免每个包都触发一次中断
描述符批量回收按batch回收对PDMA效率影响很大

如果Host侧用DPDK,中断聚合还涉及驱动里的NAPI budget和PMD轮询参数。总之,把这几个参数从“10G时代思维”里拿出来,是移植的核心工作之一。

4. 上板测试全流程:从链路建立到iperf3真实数据

4.1 测试环境拓扑

我用的是Alveo U250板卡 + QSFP28 DAC线缆直连一台Mellanox ConnectX-5 100G网卡,Host系统是Linux + DPDK,业务测试用iperf3打流。U250上CMAC走的是GTY Bank,DAC线缆比光模块省去插拔调试的麻烦,初期联调推荐这么干。

4.2 建链检查清单

上电后第一步不是跑iperf3,而是确认物理链路状态:

  • CMAC link up状态是否为1。
  • RS-FEC锁定状态是否为1。
  • rx/tx error counter持续是否为零。
  • 光模块或DAC的TX/RX功率是否在正常范围。

这几项用内部寄存器回读,全部OK才继续。链路都没up就查UDP逻辑,那是浪费时间。

4.3 Host侧准备与打流命令

Host侧使能DPDK绑定网卡:

# 配置hugepages echo 8192 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages # 绑定vfio-pci驱动 dpdk-devbind.py --bind=vfio-pci 3b:00.0

然后分别做收发包测试:

# Host -> FPGA 方向 iperf3 -c 192.168.1.10 -u -b 100G -l 1472 -t 60 # FPGA -> Host 方向需要在FPGA逻辑里设置成发包模式,或用testpmd从侧发

对于纯UDP业务,我更推荐用testpmd直接发流,便于控制包长和速率。

4.4 打流实测数据:修复前

第一轮打流结果很有意思,也是后续两个故障的引子:

UDP包长吞吐线速比例丢包情况
64B28 Gbps28%严重丢包
256B78 Gbps78%轻微丢包
1024B94 Gbps94%轻微丢包
1472B95 Gbps95%接近零丢包

大包表现不错,小包惨不忍睹。这个结果完全暴露了100G UDP小包处理瓶颈,后面故障实录二里会细讲。

4.5 抓包验证字段

打流同时用tcpdump或者wireshark抓一些样例包,重点确认源MAC、目的MAC、IPv4首部校验和、UDP长度字段是否都是预期值。抓包时有个容易误导的坑:很多版本网卡驱动默认开启RX校验和offload,wireshark抓包会显示checksum bad,实际是校验和卸载导致软件看不到正确checksum,别急着往FPGA头上扣锅。

5. 故障实录一:Link Up了却不通,先分清“ping不通”和“UDP不通”

5.1 现象:链路全部正常,但PC ping不通FPGA

板卡上电,CMAC link up、RS-FEC锁定、单板光口状态全部正常。FPGA侧ARP表里能看到PC的MAC地址,说明ARP学习功能在工作;但PC上ping FPGA的IP却不通。当时第一反应是UDP offload核有问题,准备抓链路层数据。

5.2 排错链路:把“通”的定义拆开

我先用testpmd从Host发广播包和UDP包到FPGA,结果FPGA内部计数器显示收包正常。这就说明MAC层和物理层没问题,问题出在协议响应上。

接着用ILA抓CMAC的RX和TX AXI-Stream,发现了根因:PC ping时发出的ARP request FPGA确实收到了,ARP学习也更新了,但这个开源核默认不回复ARP;同时更明显的是,ICMP echo request这个帧被UDP核直接丢弃了,因为核只处理IPv4/UDP,对ICMP报文没有响应逻辑。

换句话说,这个核的定位就是“UDP offload”,不是“完整TCP/IP协议栈”,ping不通是特性而不是bug。

5.3 修复:补一个精简ICMP echo模块

为了让工程符合大家的测试习惯,我写了一个精简的ARP responder + ICMP echo回显模块,挂在UDP核之前。ARP处理部分:收到目标IP为本机的ARP request后,查ARP表并回复reply;ICMP部分:Ethernet/IP头逐层校验,如果是ICMP echo request,就调换源目的MAC和IP后回echo reply,校验和用one‘s complement重算。

这个模块大概200多行Verilog,逻辑不复杂。加完之后ping通了,整个联调链路顺畅很多。

5.4 教训

排查“100G UDP不通”时,第一步永远是确认“不通”到底指什么:是ping不通,还是UDP业务数据不通,还是Link都不up。FPGA UDP核通常只承诺处理UDP业务包,没有义务响应ICMP。在高密度网络场景下,甚至有些硬件故意不响应ping,以减少CPU负担。这个坑看似简单,但真的会让很多人查一整天。

6. 故障实录二:64B小包只有28G,问题出在哪一层

6.1 现象:大包线速,小包崩盘

第一轮iperf3结果里大包能打到94Gbps以上,但64B小包只有28Gbps。这个差距太悬殊,肯定不是物理链路问题,因为大包能线速。

6.2 分层排查:先隔离MAC/PHY,再查用户逻辑

我第一步在FPGA内部做了个回环测试:把CMAC的RX数据直接送回CMAC TX,绕过UDP逻辑和DMA。用testpmd从Host发64B小包,测试发现回环下能把小包推到120Gbps以上,说明MAC和PHY完全没有问题,瓶颈在用户逻辑侧或者Host驱动侧。

然后我在用户逻辑各段插上性能计数器,看AXI-Stream的tready拉低比例。64B小包打流时,RX FIFO的tready只有30%左右的占比,也就是说有70%的时间下游没有能力接收新数据。瓶颈定位到消费端,而不是链路端。

6.3 根因一:描述符分配逻辑每个时钟只能处理一个包

打开描述符分配逻辑的代码,问题一目了然:原来的代码在每个时钟周期只允许处理一个包,分配描述符、写地址、更新指针都是串行状态机。当512bit接口一进来8个64B小包时,一个包占一拍,即使状态机每一拍都能处理一个包,吞吐也才到322Mpps上限;但实际上一拍能进8个包,4拍才处理3个,背压就起来了。

改动思路是把描述符分配逻辑从单包状态机改成“扫描一拍内所有包边界,并行分配多个描述符”的流水线结构。这里的关键是先用组合逻辑把tkeep转换成“包起始/结束位图”,再按位图批量处理,而不是一个个包轮流处理。

6.4 根因二:中断和描述符回收频率跟不上小包速率

修完描述符分配逻辑后,64B小包从28Gbps提升到70Gbps左右,但离线速还差一些。进一步排查发现,Host侧DPDK驱动轮询时,描述符回收的粒度太细,每个包都单独处理,导致消费者索引前进速度跟不上。

解决方案是在描述符回收端做成批量处理:每次回收处理一批已完成的描述符批次,而不是一个包一次收。同时使能中断聚合,让小包场景下每秒中断次数大幅降低。最终64B小包打到了约127Mpps,吞吐约80Gbps,对于这个工程来说已经比较理想。

6.5 修复前后对比

UDP包长修复前吞吐修复后吞吐修复后pps
64B28 Gbps80 Gbps127 Mpps
256B78 Gbps96 Gbps46.9 Mpps
1024B94 Gbps96 Gbps11.7 Mpps
1472B95 Gbps96 Gbps8.1 Mpps

100G小包线速是148.8Mpps,127Mpps虽然没到线速,但系统瓶颈已经从FPGA用户逻辑转移到了PCIe/DMA/Host驱动组合,剩余差距更多是通用方案和专用ASIC之间的差距。

6.6 教训

测试小包一定要看pps。只看Gbps会被大包成绩迷惑。64B小包打流时,一定要考问三个环节:FPGA用户逻辑能不能每个时钟处理多个包、描述符分配能不能跟上、中断和描述符回收频率会不会淹没Host。

7. 故障实录三:DMA描述符环的回绕,Host端悄悄吞包

7.1 现象:FPGA显示包已发出,Host却收不到

从FPGA往Host方向的测试中,FPGA内部发送计数显示所有包都成功送入CMAC并发出,但Host端testpmd统计到的包数少了一大截。开始怀疑是Host驱动丢了包,但反复检查驱动配置没有发现问题。

7.2 排错链路:顺着描述符生产与消费整条链查

先看FPGA侧描述符写回次数,再看中断触发次数。发现描述符写回次数和Host收到的包数对不上,说明问题出在描述符本身。

检查描述符环配置时发现,Host侧DPDK报告ring size是4096,FPGA内部掩码也是4095,没有配置错误。再查描述符基地址,发现用户逻辑里把64位基地址截断成了低32位。这台机器上Host内存分配的高地址超过4GB,FPGA把描述符写到了低4GB的某个地址,和Host期待的高地址物理内存完全是两个地方,Host自然收不到写回信息。

7.3 修复:64位地址、64B对齐、门铃顺序缺一不可

修复包括三件事:

  • 描述符基地址保持完整64位,不要在用户逻辑里做任何截断。
  • 每个描述符条目按64B地址对齐,方便DMA引擎批量读写。
  • 驱动里写tail寄存器(门铃)之前加一次内存屏障,确保描述符数据先落内存,再通知硬件去读。

特别是第三点,如果先写tail再写描述符内容,硬件可能读到半成品描述符,轻则丢包,重则地址错误。这个顺序问题在10G时代偶尔出现,100G高吞吐下会频繁暴露。

7.4 教训

DMA方向的问题,不能只看“发出去没有”这个表面现象。要把描述符生产、地址计算、写回通知、中断触发这4个环节当成一条链,逐个确认。FPGA和Host之间最隐蔽的坑往往出在地址位宽和同步顺序上。

8. 联调收尾阶段的几个经验补充

跑完这几轮测试,留一个个人体会:100G UDP移植成功的关键真的不在UDP协议本身,而在把整条数据链路当成一个流量系统来调。任何一个环节——时钟域、包处理节奏、描述符深度、中断频率、地址位宽——都会在某个包长和速率下成为瓶颈。

另一个长期有用的习惯是把FPGA内部各级计数器都做成可读寄存器。上板调试全凭计数器说话,ILA只在协议异常时抓关键波形用。计数器至少留:CMAC接收帧数/字节数、UDP核处理包数、描述符分配数、写回数、中断数、FIFO满次数包括与RX缓冲次数,哪个数字异常,问题就在哪个区间。

下一步我计划把UDP核往多个方向扩展:一个是多队列和RSS支持,让Host侧多核可以并行消费小包;另一个是把帧头解析逻辑做成可配置规则,为后续接入RDMA或自定义硬件协议预留基础。如果你也在做100G FPGA UDP,希望这份记录能帮你少踩几个坑。

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

生产级格式转换工具链:FFmpeg+ImageMagick+Poppler+Tesseract深度集成

1. 这不是又一个“点一下就转好”的工具,而是真正能扛住生产级任务的格式处理中枢你有没有遇到过这些场景:剪辑完的4K视频导出后太大,发给客户前得压到50MB以内但又不能糊;会议录了2小时的MP3,领导要文字稿&#xff0c…

作者头像 李华
网站建设 2026/9/9 9:24:08

TypeScript keyof 从入门到实战:类型安全的键提取与映射类型解析

1. 为什么说 keyof 是类型系统的“钥匙” 1.1 keyof 到底返回了什么 很多人第一次看到 keyof 的时候,以为它只是“把一个对象的键取出来”。这个说法不算错,但太粗糙了。我更喜欢把它理解成: TypeScript 类型系统里唯一能从“对象形状”中提…

作者头像 李华
网站建设 2026/9/9 9:24:00

混合信号验证核心:RNM抽象与Verilog-on-Top协同方法

1. 这不是纯数字验证,也不是传统模拟仿真——混合信号验证到底在验什么?“MSDV”这个词最近在芯片验证圈里出现频率越来越高,但很多人一听到就下意识觉得是“数字验证的延伸”,或者干脆当成“带点ADC/DAC的数字流程”。其实完全不…

作者头像 李华
网站建设 2026/9/9 9:23:26

Transformer核心原理与PyTorch实现:从Attention机制到位置编码

深度学习圈子里这几年有一个词几乎无人不知:Transformer。哪怕你不是做自然语言处理的,也一定在计算机视觉、语音、推荐系统、时间序列预测这些方向里反复撞见它。很多人第一次读《Attention Is All You Need》这篇论文时,都会有一种“每句话…

作者头像 李华
网站建设 2026/9/9 9:23:22

TypeScript接口:从类型契约到架构设计的实战指南

如果你有两年 TypeScript 实战经验,大概率会经历这样的转折:刚上手时觉得 interface 无非就是给对象写个模板,比 any 高级一点;直到某天你面对一个被 20 个业务方共同引用的接口,改动一个字段名,编译器瞬间…

作者头像 李华