100G UDP协议栈FPGA移植实战:从RTL修改到上板调通全记录
2026/9/20 1:38:25 网站建设 项目流程

100G UDP移植上板这件事,说起来不算新鲜,但真要把一套开源协议栈在自家板卡上跑通、跑到线速,中间踩的坑绝对比你想象的多。我前阵子刚完成一轮基于开源代码的100G UDP协议栈移植,从RTL修改、时序收敛到上板实测,前前后后折腾了小两周。这篇文章就把整个过程中的关键环节和实测记录整理出来,给准备入坑或者正在填坑的朋友一个参考。

1. 项目整体设计与思路拆解

1.1 为什么选开源UDP协议栈而不是自己造轮子

做100G网络处理,第一道选择题就是协议栈怎么来。自己写一套完整的UDP/IP协议栈,光ARP、ICMP、IP校验和、分片重组这些基础模块,工作量就是按人月算的,而且Debug周期极长。用开源方案的话,目前主流选择其实就那么几个,各有各的脾气。

我用的是GitHub上star比较多的那套verilog-ethernet,作者是Alex Forencich。选它主要看中几点:一是支持100G,XGMII接口和1024位数据总线都齐;二是模块划分干净,MAC、IP、ARP、UDP各层解耦清晰,方便单独替换和测试;三是License宽松,商用也没问题。

提示:如果只做40G或者25G,也可以考虑其他轻量级方案,但100G这个速率档,能直接拿来改的成熟开源栈其实不多,verilog-ethernet基本是绕不开的选择。

1.2 移植的整体架构与数据流设计

我在移植前先画清楚了整条数据通路,这里也列出来给你参考:

用户逻辑 → UDP发送FIFO → IP发送 → MAC发送 → 100G PHY → 光模块 用户逻辑 ← UDP接收FIFO ← IP接收 ← MAC接收 ← 100G PHY ← 光模块

核心控制模块我分了三块:UDP栈配置寄存器组,用来设置本地IP、MAC、端口号;ARP缓存表,用于MAC地址学习和查找;用户数据接口,我选的是简单的AXI-Stream FIFO接口,方便和DDR或者用户逻辑对接。

1.3 为什么用户逻辑必须挂在FIFO后面

这个问题我一开始没太在意,结果在时序收敛阶段吃了大亏。直接把用户逻辑接在UDP栈的接口上,组合逻辑路径太长,100G下时钟跑到322MHz(其实用户逻辑时钟通常是322MHz左右,按数据位宽1024位计算,线速大约是322MHz * 1024 bit ≈ 330Gbps,远高于100G需求,实际不需要这么高时钟),时序根本收敛不了。

后来改成FIFO解耦,用户逻辑跑在自己的时钟域,UDP栈跑在MAC时钟域,中间通过异步FIFO过渡,时序问题才彻底解决。这个设计思路务必记住,100G下几乎不可能让整条通路都跑同一个时钟域还保持时序收敛。

2. 核心细节解析与实操要点

2.1 100G MAC层如何处理64B/66B编码

这部分属于物理编码子层的内容,但直接决定了你在RTL层怎么和PHY对接。100G以太网使用64B/66B编码,每64位数据块前增加2位同步头,同步头用于区分数据块和控制块。FPGA内部的MAC核通常已经处理了这部分,对外接口是XGMII或者XLGMII。

我用的Xilinx 100G Ethernet MAC硬核,配置成带FEC的RS-FEC模式,然后接到光模块。这里有个重要选择:FEC开还是不开。FEC(前向纠错)能显著降低误码率,但会增加约2-3纳秒的延迟,并且会占用部分带宽(约6.7%的开销)。我在板卡上实测,短距离光纤(1米以内)不开FEC完全没问题,但如果你的光纤链路超过10米,强烈建议把FEC打开。

注意:FEC模式必须在MAC核配置阶段就定好,不能运行中动态切换,否则链路会直接断掉。

2.2 ARP模块的两种用法,别搞混了

这套开源栈里自带ARP模块,支持两种模式:一种是主动模式,定期发送ARP请求维护邻居表;另一种是被动模式,只在收到ARP请求时回复,同时学习对端MAC。我做的是点对点直连测试,用的是被动模式加静态ARP表项,把对端的IP和MAC预先写进去,省去动态学习的麻烦和等待时间。

初始化时往ARP表里写入对端MAC的Verilog代码大致是:

// 静态ARP表项写入示例 arp_table_entry[0].ip_addr <= 32'hC0A80001; // 192.168.0.1 arp_table_entry[0].mac_addr <= 48'hA088B1880001; arp_table_entry[0].valid <= 1'b1;

这种写法在仿真和上板时都稳定,适合链路伙伴固定的场景。

2.3 UDP校验和到底怎么算才对

UDP校验和计算是很容易出问题的地方。协议上规定,UDP校验和包含伪头部(伪头部含源IP、目的IP、协议号、UDP长度),如果校验和计算错误,很多网卡会直接丢包。开源代码里通常提供两种选择:计算并填充校验和,或者置为零(IPv4下允许不校验)。

我实测发现一个问题:如果直接把校验和字段置零,Linux主机端默认可以接收,但Windows主机端大概率会丢包。所以我的建议是老老实实把校验和算对,代码库里一般有现成的checksum模块,直接用就行,不要在这个地方省事。我在仿真时专门写了测试用例,验证源IP、目的IP任一变化时校验和都会相应变化,避免上板后出现玄学丢包。

3. 实操过程与核心环节实现

3.1 开发环境与板卡资源清单

先交代一下我的环境,方便你对照参考:

  • FPGA芯片:Xilinx UltraScale+ VU9P
  • 100G MAC:Xilinx 100G Ethernet MAC硬核
  • PHY:集成在板卡上的100G光模块接口
  • 开发工具:Vivado 2021.2
  • 开源协议栈:verilog-ethernet(从GitHub拉取的最新commit)

资源占用情况大致是:LUT用了大概45K,FF约38K,BRAM用了86个(主要是FIFO和ARP缓存),DSP基本没用到。整体资源占比在VU9P上很轻松,即使并两个100G端口也够用。

3.2 移植流程的详细步骤记录

整个移植过程我拆成了五步,每一步都有明确的验证节点,不要跳步:

第一步:先仿真后上板。先把代码库里的tb跑通,确认MAC、IP、ARP、UDP模块的功能在自己定的参数配置下没问题。这里我推荐把仿真时间拉长一些,比如发送100万个包,验证长时间运行的稳定性,而不是只跑几个包就完事。

第二步:修改接口宽度和时钟。开源代码默认可能有多种配置,100G下数据总线宽度是512位还是1024位要看MAC核配置。我的做法是统一用1024位,时钟频率降到322MHz左右(实际上是为主逻辑提供宽松的时序余量)。这一步不需要改逻辑,只改参数,但要全局搜索一遍所有宽度定义,漏改一个位宽匹配都会导致数据错位。

第三步:对接MAC硬核。把开源栈的MAC模块替换成Xilinx自己的硬核,接口信号做适配。硬核的AXI-Stream接口是tkeep/tvalid/tready/tlast这种标准握手,但数据位宽和时钟必须和MAC核完全一致。这里最容易出错的是tuser信号,硬核要求tuser标识每个包的起始字节位置,配错了整包数据都是乱的。

第四步:用户逻辑对接。在UDP栈上面封装一层简单的AXI-Stream到FIFO的桥接逻辑,把收发数据接入自己的处理模块(我这边是一个简单的DDR缓存逻辑,用来做大包收发测试)。

第五步:上板调试。用ILA抓关键信号,先用回环模式验证PHY和MAC层,再逐步打开IP和UDP层。建议调试时先用小包,比如64字节,把通路打通后再切成大包。

3.3 上板测试的完整步骤

上板测试我分了三轮:

第一轮:PHY层回环测试。在MAC核里配置近端回环(near-end loopback),不经过光模块,验证FPGA内部发送到接收的链路是否通。此时ILA能看到TX和RX的数据完全一致。

第二轮:光纤直连回环测试。用一根光纤跳线把光模块的TX和RX直接短接(或者通过交换机回环),走完整的物理链路。这一步能把PHY、光模块、SFP+接口的问题暴露出来。我遇到的问题是光模块的TX_DISABLE引脚没拉低,导致光口完全没有光功率输出,用光功率计一测才知道。

第三轮:对接主机实测。把板卡和服务器网卡用光纤连起来,配好IP地址(板卡设为192.168.0.2,网卡设为192.168.0.1,掩码255.255.255.0),用iperf3或者自定义的UDP工具打流。这一步能验证整条协议栈的真实吞吐和稳定性。

3.4 硬件连接和配置细节

硬件连接这步看着简单,但有几个细节直接影响测试结果:

  • 光模块的速率必须匹配,100G SR4模块配MPO光纤跳线,如果是QSFP28接口要注意区分SR4和LR4。
  • 板卡上电后先确认光模块的los信号是拉低的,代表有光信号输入。
  • 服务器网卡如果是双口卡,确认插的是哪一口,别测了半天发现测试流量根本没进被测链路。

4. 常见问题与排查技巧实录

4.1 现象:收发都不通,ILA看不到任何数据

这是上板第一天的经典问题。排查路径是从里往外查:先看复位信号是否释放,再看MAC核是否完成初始化,然后看光模块los和tx_fault信号。我在这个环节发现一个坑:开源代码的复位逻辑是上电后自动复位一段固定时间,但100G MAC硬核需要的复位时间更长,导致MAC还没准备好就开始发数据了,状态机卡死在初始化。

解决办法是改成等待MAC核的tx/tx_ready信号拉高后再释放用户逻辑复位,用状态机做一次握手。

4.2 现象:主机ping不通FPGA

Ping不通时,先抓包看是否有ARP请求进来。如果ILA能看到ARP请求但FPGA没有回复,基本就是ARP模块的IP和MAC配置不对,或者校验和模块把包丢弃了。我遇到的情况是ARP缓存的初始化文件没写对,MAC地址的高低字节顺序反了(大端小端问题),导致回复的ARP包携带的源MAC是错的。

排错心得:所有涉及MAC地址的地方,先确认字节序。这是以太网协议里最容易出错的隐性规则,PowerPC/x86主机和FPGA之间的字节序差异坑了无数人。

4.3 现象:小包能通,大包丢包严重

这种情况基本指向FIFO深度不够。小包的时候数据量小,FIFO能缓存住;大包一来,瞬时突发流量超过FIFO容量,数据就丢了。100G线速下,一个1500字节的包,在1024位总线宽度下需要约12个时钟周期才能发完,但用户逻辑写入速度如果跟不上,FIFO就会被写满。

解决办法:

  • 增加发送FIFO深度,我改到了32K字节。
  • 用户逻辑侧做反压,fifo_prog_full信号拉高时暂停写入。
  • 如果是双向收发,务必使用独立FIFO,避免读写冲突。

4.4 100G测试中掉帧和延迟抖动的排查

100G网络测试中,掉帧和延迟抖动是最让人头疼的。我在测试中发现,当发送速率接近满速(比如95Gbps以上)时,偶发掉帧。用ILA抓包发现,掉帧时刻恰好是FIFO的prog_full信号拉高瞬间,原因是在这个周期用户逻辑还在继续写入,造成了一拍竞争。

修正方式是把prog_full阈值调低一些,比如FIFO深度16K时,prog_full设为12K,留出足够的反应余量。这招非常有效,调完之后打流2小时零掉帧。

4.5 常见问题速查表

现象可能原因排查方法解决方案
完全不通复位时序问题ILA查复位状态等待MAC ready后再释放复位
完全不通光模块无光光功率计测TX检查TX_DISABLE引脚
小包通大包丢FIFO深度不足看fifo_full计数加深FIFO、加反压
ARP不通字节序错误抓包对比MAC修正字节序
偶发掉帧FIFO阈值竞争ILA对比时序调低prog_full阈值
速度上不去AXIS总线位宽不匹配看数据tkeep统一总线位宽

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

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

立即咨询