拓冰建站拓冰建站
首页 / 资讯中心 / 正文

100G UDP协议栈FPGA移植实战:架构、时序与上板调优

1. 为什么非要把100G UDP搬到FPGA上1.1 100Gbps线速并不是“很快”两个字能概括的100Gbps换算一下满双工是12.5GB/s。如果按最小以太网包计算也就是64字节帧带着8字节前导码和12字节帧间隙实际占用链路的时间折算下来一秒钟要处理大约1.48亿个包。这个包速率面前纯CPU跑软件协议栈基本没有活路每来一个包都要触发中断、进出协议栈、拷贝数据CPU主频再高也会被打爆。这也是为什么现代高速网卡都在硬件里做RSS、TSO、LRO这类卸载机制。说白了机器本身已经算不过来了必须把每个数据包的解析、分片、校验和组合这些重复动作交给专用硬件流水线。而在FPGA里做一套100G UDP卸载本质就是把网卡在硬件里做的这一套东西搬到自己能控制的开源逻辑里来。数据包从QSFP28光模块进入FPGA经过物理层、MAC层之后由UDP引擎剥掉以太网头、IP头、UDP头把payload直接交给用户逻辑。整个过程没有操作系统调度开销也没有协议栈上下文切换每一级都是纯硬件流水线在跑。我前一阵做开源100G FPGA UDP移植上板测试时最直白的感受是以前调千兆网口问题一个示波器加抓包软件基本够用到了100G这个量级光看波形已经看不出所以然必须靠MAC层统计、PCS状态、UDP引擎计数器这些内部信号一点点定位。这也是为什么我强烈建议任何要做这类移植的人先把设计里的统计模块研究透。1.2 开源方案解决的是“从0到1”的工程门槛自己从零写一套100G UDP协议栈不是不行但工作量的重心根本不在“写代码”而在“验证”。你要覆盖各种帧长、错包、CRC错误、PCS对齐、自动协商还要应付不同光模块和线缆之间的互操作单独拉出来都是一长串测试用例。开源设计的好处是MAC、PCS/PMA、收发器这些底层接口已经被定型了甚至在大量板卡上验证过。你后续要做的事情主要是适配和集成而不是重新发明轮子。这也是“移植上板测试”这件事真正有价值的地方你在验证一套已经成熟的开源设计能否在特定板卡、特定工具链版本、特定光模块环境下复现它的功能。我把这次的目标定得很明确第一把开源设计的100G UDP收发跑通第二测试它在真实线缆、光模块、交换机环境下的吞吐量第三把移植过程中踩到的问题整理成一套可复现的排查方法。1.3 移植测试要回答的必答问题移植前我习惯先给项目列一个验收列表后面每一步都对照检查板卡FPGA型号与资源够不够包括高速收发器数量、逻辑单元、Block RAM光模块和QSFP28连接方式与源工程是否一致以太网MAC IP的配置能否在新工具链里正常生成时钟频率、复位时序是否在数据手册允许范围内综合后时序能否收敛上板后链路能否完成link training能不能通过打流、抓包、CRC统计等多维度判断功能正确性。这六个问题看起来基础但在后面的移植过程中每一个都可能单独卡你几天。2. 开源100G UDP方案盘点与内部架构2.1 几类典型的开源设计在GitHub上搜100G UDP能找到的大致分三类。第一类是以网卡应用为背景的开源工程典型代表有Corundum、OpenNIC这类。它们通常包含PCIe DMA、队列管理、MAC/PCS以及比较完整的收发数据通路适合做真正的智能网卡原型。但配套的还有驱动和主机侧软件如果只是想验证UDP协议栈这套东西偏重。第二类是偏IP核形态的开源以太网组件比如常见的verilog-ethernet系列。它把10G/25G/100G MAC、ARP、IP、UDP等模块分开你可以按需组合。这类方案结构清楚方便替换某些环节也容易看懂数据流更适合做二次开发。第三类是高校或研究机构放出来的验证型工程一般针对某块特定开发板做过适配。代码能跑但文档往往不全且经常绑定特定Vivado版本和板卡引脚移植成本反而可能更高。我的建议是如果只想验证UDP收发没有主机端驱动开发的需求优先选偏IP核形态的方案因为它把各层逻辑拆得干净方便替换MAC、PCS和你自己的用户逻辑。如果最终目标是做一块完整网卡再考虑Corundum、OpenNIC这类带PCIe驱动和DMA框架的重型方案。2.2 一个典型100G UDP协议栈的分层我这次移植的对象架构大致如下物理层GTY高速收发器负责把QSFP28光模块的电信号转成并行数据完成串并转换和时钟恢复PCS/PMA层做64B/66B编解码、通道对齐、FEC等Xilinx的CMAC IP一块全包用户侧看到的是AXI-Stream接口UDP/IP层这是开源工程的核心部分。接收方向上解析以太网头判断VLAN、IPv4/IPv6查UDP目的端口是否匹配校验IPv4头校验和和UDP校验和然后把payload与包描述符成对交给用户逻辑发送方向上从用户逻辑拿到payload填充MAC地址、IP地址、UDP长度与校验和再送到MAC。用户接口通常是AXI-Stream数据位宽从256位到512位不等。位宽越大时钟越低但一条总线上有效数据信号、起始结束标志、错误标志都要跟着切对。移植时最容易忽略的是tuser里的包起始标志和错误标志。不同开源工程对这个信号的位宽和含义约定不同理解错一拍你的用户逻辑就会丢第一个包或者整包错位。2.3 区分“协议栈”和“板级Demo”能省一半时间我看过不少朋友直接把板级Demo整个目录拿过来编译结果工程里自带一堆只对某个厂家某型号有效的约束文件、IP配置和引脚分配稍不注意就陷进跟协议栈毫无关系的坑里。真正值得复用的是协议栈那部分RTL板级Demo只是一个参考顶层。我在移植时会把“协议栈相关模块”和“板级适配相关模块”在工程目录里分成两个区域明确哪些文件来自源仓库哪些是我新增的引脚约束和时钟字典。这样后面一旦出问题定位起来会快得多。至少你不会把板卡自身的问题和协议栈的问题混在一起查。3. 移植到开发板前的资源盘点与工具链准备3.1 板卡资源是否满足先看四个指标100G不是一个中等规模FPGA就能随便跑起来的。我这次用的开发板是Xilinx UltraScale级别板上有QSFP28光口GTY收发器数量足够。具体看四个东西高速收发器数量至少包含一组QSFP28 lane也就是4个收发器如果后续要做多通道聚合还要更多逻辑资源和BRAM/URAM要放得下整个协议栈和用户侧缓冲队列时钟芯片是否能生成MAC/PCS需要的参考时钟调试外设有没有串口、LED、PCIe接口方便上板后观察状态和搬运数据。选型建议上如果只是做纯UDP验证不要一上来就选最大的片子成本高综合时间也长。手头已有板卡的话先把源工程在新工具链里综合一遍看资源占用量再决定用户逻辑能加多少。3.2 工具链版本与许可证这关最容易翻车开源工程在不同Vivado版本上打开IP核经常要执行Upgrade。有时候源工程用的以太网MAC IP版本比较老新版Vivado升级后会要求重新生成PCS/PMA配置生成完你还得重新核对参数比如TX/RX流控、FEC开关、时钟模式。更要命的是许可证。有些以太网IP核是商业授权Vivado社区版根本调不出来。我这次的做法比较保守先看工程目录下IP核的license状态确认没有商业IP依赖再选一个和源工程发布时间接近的Vivado版本而不是最新版。新版本对老工程的兼容性不一定更好反而可能因为IP版本升级引入一堆无关问题。3.3 哪些文件能复用哪些必须重写先说能复用的协议栈的RTL源文件、IP核配置如果工具能识别、仿真testbench、文档里的端口列表。必须重写的顶层引脚约束XDC、时钟约束频率、复位时序以及任何跟板载DDR/PCIe相关的桥接逻辑。即使是同一个厂家两块板卡的QSFP28引脚bank也可能完全不同。100G收发器一旦引脚错配布线基本必失败。所以移植前第一件事就是拿着目标板原理图把QSFP28的TX/RX差分对、参考时钟、模块复位脚、电源监视脚一条条核对一遍再写新的约束文件。这一步省不了也别指望源工程里那份约束能直接套用。4. 移植实操顶层改造、时钟约束与流水线调整4.1 GTY收发器与光模块引脚适配源工程一般带一个top.v或者fpga_top.v里面是收发器例化和对应的约束。移植时我习惯新建一个board_top.v作为壳把源工程的协议栈核心包进来只改IO相关部分。关键点包括差分时钟端口比如gt_ref_clk_p/n要接到QSFP28模块的参考时钟引脚不要接成普通全局时钟收发器TX/RX差分对按原理图分配注意极性不能反复位引脚板卡上可能是低有效也可能是高有效要跟源工程对齐光模块的I2C管理引脚如果源工程有对应逻辑最好保留不然某些模块没做初始化会一直link不上。我在第一次上电时就遇到过link死活起不来的情况后来发现是GTY的参考时钟被接到了普通用户时钟引脚上收发器根本锁不住频率。这个问题在仿真里看不出来只有上板才会暴露。4.2 时钟字典与跨时钟域处理100G MAC的user clock和数据路径时钟不一定在同一个频率域。MAC输出到用户的时钟可能跑到200MHz以上而用户业务逻辑可能需要更低频率。跨时钟域的地方必须用异步FIFO包一层不能简单用寄存器打几拍完事。源工程一般已经提供了FIFO接口但替换用户逻辑时要保留完整的backpressure路径。否则FIFO一满上游MAC还在持续灌数据丢包就是必然结果。我自己做过一次对比实验把用户侧直接接成“永远tready1”来测吞吐数字非常漂亮但那个数据其实没有真正被处理只是测了总线理论最大值。后来改回真实FIFO写DDR再打回速率立刻回到正常水平丢包计数也才变得有意义。这提醒我上板测试的统计口径一定要和自己实际的数据通路对应起来。4.3 用户侧AXI-Stream接口改造的几个关键信号如果要把payload送去业务逻辑重点关注这几个信号tvalid/tready握手必须遵循AXI-Stream规范不能一端不拉低就默认对方一直就绪tlast标记包结束。很多逻辑只判断包长没判断tlast最后一个拍的数据到不了用户缓冲区tuser里通常携带错误标志比如CRC错误、长度错误不能一丢了之至少要接到统计接口tkeep在包尾不是全1收端必须按它来裁剪有效字节否则送进DDR里的payload会混入无效字节。我在做回环测试时专门用ILA抓过短包的tkeep波形。因为短包的尾字节数不定处理不对就会多传或少传4字节。这个问题在功能仿真里未必暴露只有上板打短包时才能发现。4.4 时序收敛组合路径拆流水综合属性别乱删100G设计对时序要求很苛刻。我第一版综合后时序报告里有一堆负slack集中在协议栈里跨时钟域的地址指针信号上。排查后发现源工程里某些计算路径原本依赖(* max_fanout 64 *)这类综合属性来限制扇出我在新工程里没保留导致走线压力过大时序不收敛。解决方式是恢复这些综合属性同时在数据路径上增加流水级把长组合逻辑拆成几步。做UDP校验和时有些开源实现用了一个大组合逻辑直接算32位加法链逻辑深度很大主频一高必挂。解决办法是在校验和路径内部插入多个流水级延迟增加几个周期但吞吐率不受影响。综合前先跑一次report_qor_suggestions按工具提示加约束比盲目改代码效率高得多。这条经验我每次移植都要重复一遍因为每个工程的情况都不一样。5. 上板后的真实测试打流、抓包、吞吐量与丢包5.1 测试拓扑与仪器准备我这次用了两种测试拓扑。第一种是端到端拓扑FPGA的QSFP28口通过100G DAC线或光模块直连服务器上的100G网卡网卡侧跑iperf3。这种拓扑能直接看到端到端收发状态也最接近真实应用场景。第二种是回环拓扑FPGA内部把TX数据来源固定成测试pattern或者把RX的MAC回环打开只观察协议栈内部的收发计数器。回环拓扑适合定位问题在FPGA内部还是外部链路上但它不能证明端到端正确性。软件方面我准备了三个iperf3用UDP模式打流Wireshark或tcpdump抓包看UDP payload和checksum如果手头有100G Spirent或IXIA测试仪当然更好但没有的话一张支持100G的高性能网卡加iperf3也能做初筛。5.2 iperf3 UDP打流命令与结果解读网卡侧的发送命令可以这样写iperf3 -u -c 192.168.10.20 -b 80G -l 1024 -t 60 -P 8含义是向FPGA侧IP地址发UDP流目标带宽80Gbps包长1024字节开8个并发流持续60秒。为什么不开满100G因为如果网卡或交换机本身跑不满100G线速开满反而会因为链路层backpressure引入没有意义的丢包。先80G看效果再逐档往上加。FPGA侧需要有对应端口能查统计寄存器。开源工程一般会带一个stats模块能看到total_packets、total_bytes、crc_err、overrun等计数。对比网卡侧发送包数和FPGA侧接收包数差多少就是丢包数和丢包率。这里有一个容易踩的地方不要把packets received和packets to unknown port混在一起看。UDP端口不匹配时包会被协议栈正常丢弃但统计记在unknown port上。这并不代表链路有问题只能说明你的用户逻辑没有在监听那个端口。5.3 别被吞吐量数字骗了如果看到“发了80G实际收到80G”就觉得任务完成那还差得远。还要看几件事CPU占用如果iperf3发送时CPU已经打满那80G其实是软件协议栈的上限不是链路能力包长变化小包能跑满才是真本事大包跑满只能说明MAC没有问题错包校验在Wireshark里看IPv4头校验和、UDP校验和是否为正常值UDP校验和显示bad说明checksum offload没生效CRC错误MAC层统计到非零CRC错误说明物理层或PCS有问题这种丢包后面再怎么调UDP也没用。我实测下来1500字节大包时吞吐可以非常接近100G线速但改到64字节小包时如果用户逻辑每包处理开销大很容易跌到60G左右而且丢包集中在同一批相似包长上。原因基本都是多包描述符的流水线深度不够处理不过来就反压。5.4 100G线速的真正瓶颈是包速率线速要分开看对大包线速容易跑满对小包关键指标是PPS。100G以太网的线速PPS大约是148.8M换算一下两个包之间的间隔只有6.7ns左右。FPGA内部任何一个需要顺序处理多包的状态机如果处理一个包需要10个周期主频200MHz那每秒最多只能处理20M包远远不够。所以真正要支撑小包线速必须用多流水线或多队列并发设计。开源工程通常只是给一个基础通路这部分性能上限你最好用计数器提前算清楚。不要等到现场压测才发现不够再回头拆流水线那个改动量会大得多。6. 移植过程中最容易翻车的几个问题与排查链路6.1 复位顺序没做对CRC错误飙升我第一版上板链路link是up的但RX CRC错误一直在涨。查了很久最后定位到是复位时序不对MAC的rx_reset和PCS reset释放顺序反了。按照数据手册必须先把PCS/PMA复位释放等PCS完成alignment之后再释放MAC侧复位。如果反过来MAC会尝试在PCS还没准备好的时候收数据收到的全是噪声CRC自然全错。解决方法是按IP核数据手册里的复位释放顺序图在顶层用状态机产生完整复位序列不要用一个全局低电平复位一把梭。6.2 IP地址字节序反了Wireshark显示一切正常但业务不通还有一个比较隐蔽的问题出现在IP地址的配置寄存器上。网络字节序是大端FPGA内部总线一般按小端习惯处理。如果配置IP地址时把host order和network order搞混能收到的UDP包Wireshark里看到源IP和目的IP是正常的但用户逻辑从寄存器里读出来做路由判断时永远匹配不上。这个问题最典型的症状是打流看起来全通但FPGA侧按IP过滤统计的计数器一直是0。排查时不要只盯着payload层先确认协议栈配置IP时用的是host order还是network order再用一个已知源IP的测试包从头到尾走一遍多路选择逻辑。IPv4头校验和、UDP校验和也一样伪首部里包含源IP、目的IP、协议号、UDP长度任何一个字节序不对校验结果就会差出“看似偶尔正确实则规律性错误”的结果。6.3 背压信号晚一拍FIFO溢出丢包调试中我遇到一个很隐蔽的问题偶尔丢包而且丢包数量和速率没有规律。后来用ILA抓取RX路径的tready发现用户逻辑在FIFO快满时确实拉低了tready但拉低的时机太晚MAC已经把一整段数据发出来了FIFO溢出导致直接丢包。这个问题要从两个方向改。一是把FIFO的水位阈值调低一些留出足够余量二是让tready的变化以组合逻辑直接反馈给上游而不是在状态机里先判断再下一拍拉低。在200MHz以上的高速时钟下晚一拍往往就晚了一整个数据包周期。6.4 小包性能不够先看用户逻辑的处理粒度小包跑不满经常被归咎于MAC或协议栈但有时候问题在用户逻辑本身。如果你的用户逻辑按“整包处理”来设计必须先等tlast到来才知道包结束再开始后续DMA搬运或内容处理那每个包都至少增加一个包长的缓冲延迟。解决办法是改成“边收边处理”在tvalid有效时就开始解析头部、计算元数据tlast到来时直接把包描述符交给下游。这种流水线思路对短包尤其重要因为短包的包头占比高处理延迟对吞吐的影响会被放大。6.5 QSFP28模块与FEC配置不匹配光模块和DAC线的兼容性也会让人抓狂。100G有SR4、LR4、CR4等不同标准模块类型和链路两端必须匹配。MAC/PCS里的FEC配置也要一致。如果两端一边开了FEC另一边没开link可能能up但丢包和CRC错误会不定期出现。我最后是靠带完整寄存器读回的对端网卡和模块逐个确认了FEC模式、模块告警状态才把链路稳定性问题解决。移植测试时这个不是算法问题但最容易让人在UDP层查半天查不出结果。最后说一点个人经验做这种开源IP移植一定要保留原设计自带的状态寄存器和统计计数器哪怕你觉得当下用不上。上板测试阶段我全靠这些计数器判断问题出在MAC层、PCS层还是UDP引擎效率远高于单纯去抓波形。开源工程拿到手先不要急着加自己的功能只要把原有loopback跑通就已经过了最困难的一半。后面想继续往里塞自己的pipeline、DDR搬运逻辑或者RDMA相关模块再一层一层在外围加每一步都还可以用这套100G测试环境去验证。这也是我觉得这次移植上板测试最值回票价的收获。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门