ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

FPGA 100G UDP协议栈开源移植实战:从CMAC适配到线速打流

FPGA 100G UDP协议栈开源移植实战:从CMAC适配到线速打流 这个项目的起因其实很直接——手头拿到了一个带双QSFP28接口的高端FPGA板卡核心逻辑资源、DDR4和PCIe硬核都很充裕但网络侧只有MAC/PCS的硬核没有现成的UDP协议层。问了一圈商用IP授权价格让人倒吸一口凉气。于是把目光转向了开源方案目标非常明确把一套开源的100G UDP协议栈移植到这块板卡上做成能实际收发用户数据的参考设计再用打流工具把性能测个明明白白。这个工程做下来踩坑不少但收获非常大。整套流程从选型、移植到上板测试几乎覆盖了FPGA高速网络开发会遇到的全部典型问题时钟怎么规划、CRC字节序怎么处理、跨时钟域FIFO怎么设计、线速能不能跑满、小包PPS为什么上不去、PC端UDP接收缓冲区怎么调。这篇文章我会把这套完整的移植和测试记录整理出来给准备做10G/25G/100G UDP方向的同学一份可以直接抄作业的参考。1. 开源方案选型为什么选它而不是自己写先说结论我们最终选择了Alex Forencich维护的verilog-ethernet开源协议栈。这个项目在GitHub上非常有名想碰高速以太网的FPGA工程师基本绕不开它。选择它的核心原因有三点接口标准化程度高、对Xilinx硬核MAC的适配非常完善、社区活跃度足够。当然也不是说只有这一个选项COREnite的10G/25G UDP方案也可以参考但那个更偏向商用授权开源友好度不如前者。另外OpenCores和GitHub上还有一些零散的UDP核但大多数只做到1G/10G接口风格也很不统一扩展性差。选型对比其实很关键我整理了一张表方案最高速率接口风格商用授权风险100G支持维护状态verilog-ethernet100GAXI4-Stream / 自定义总线无MIT类支持CMAC硬核适配更新活跃COREnite UDP/IP25G自定义需商业许可证无稳定但更新慢自研UDP设计视能力而定自定义无无需自行维护全部时序对比完就懂了自研UDP协议栈在10G以下还可行越往上越痛苦。100G以太网的PCS/PMA层基本不可能用纯FPGA逻辑实现必须依赖UltraScale的CMAC硬核或者Intel的E-tile硬核。协议栈真正要做的核心工作是MAC层之上的IP解析、ARP应答、UDP头解析和校验和计算这部分逻辑量其实不大但关键路径时序和跨时钟域设计才是真正的难点。verilog-ethernet这个仓库里自带了一个完整的UDP/IP协议栈模块udp_ip它把MAC层接收到的帧做解析后直接丢给用户逻辑用户逻辑需要回包时把数据塞进TX接口就行。它还实现了ARP协议这在局域网调试时非常重要——PC端要先发出ARP请求拿到FPGA的MAC地址UDP包才能真正发出去。如果协议栈里没有ARP处理你会遇到非常诡异的“PC发送端显示发送成功但FPGA收不到wireshark里显示PC一直在广播ARP请求”的问题。选型时还要注意一个点官方仓库的例程和文档主要针对10G/25G场景100G的例程相对少一些需要自己把hd接口改成对应CMAC的512bit或者64bit数据位宽。这意味着你必须有基本的FPGA数字逻辑功底如果完全新手建议先从10G版本跑通再往100G迁移。2. 软硬件环境与关键原理准备2.1 硬件平台与时序规划我手头的板卡核心芯片是Xilinx UltraScale VU9P有两个QSFP28接口每个接口物理层对应四路25.78125Gbps的高速SerDes加上RS-FEC后总带宽正好是100Gbps。用的FPGA型号支持CMAC硬核这几乎是100G UDP方案的必经之路——没有硬核PCS纯逻辑想跑到上百Gbps的线速率时序根本收敛不了。时钟规划是整个移植中最容易被低估的环节。100G以太网跑的是25.78125Gbps线速率GTY参考时钟通常是161.1328125MHz由QSFP28模块参考晶振分频得到CMAC内部会把这个时钟倍频到所需的SerDes速率。而用户侧AXI4-Stream接口的时钟则取决于数据位宽如果用512bit位宽时钟约是322.265625MHz如果用64bit位宽时钟频率就得跑到1.6GHz以上这在FPGA里根本不可行。所以开源协议栈在100G场景下几乎都推荐用512bit数据位宽内核逻辑跑到322MHz左右还算轻松。移植之前一定要和板卡的硬件手册仔细核对QSFP28的参考时钟频率是161.13MHz还是155.52MHz这个必须和硬件设计匹配错了链路直接起不来。CMAC复位时序要参考Xilinx官方例程尤其要注意tx/rx reset done信号何时拉高顺序错了会导致链路状态机卡死。QSFP28模块的I2C管理接口通常挂在I2C控制器上需要在上板时配置成正确的线速率模式否则模块可能识别为4×25G的Breakout模式而不是当作单端口100G使用。2.2 数据通路与协议栈结构整个UDP数据通路的架构是这样的QSFP28光模块接收光信号经GTY SerDes进入CMAC硬核CMAC完成64B/66B编解码和PCS成帧后把AXI4-Stream格式的MAC帧交给用户侧逻辑。用户侧逻辑里第一个模块是MAC RX FIFO主要作用是做跨时钟域和带宽匹配。接着进入UDP/IP协议栈它解析MAC头、IP头、UDP头提取出payload数据同时校验CRC、IP校验和以及UDP校验和。发方向完全对称用户数据打包成UDP报文加IP头、MAC头再通过CMAC发出。这个过程中有一个特别容易被新手忽略的细节就是字节序问题。以太网协议是大端序而FPGA内部大多数Design上习惯使用小端流水如果不做字节翻转你会发现抓包工具显示的目的MAC地址字节是反的IP地址也从 192.168.1.10 变成了 0A 01 A8 C0 这种逆序。开源协议栈内部已经处理好了字节序但你自己写的用户逻辑如果对接它的接口必须按照它定义的字节序来填充IP地址和UDP端口否则包出去了对方设备根本认不出。另外UDP校验和的算法也很容易写错。UDP over IPv4的校验和是计算伪头部源IP、目标IP、协议号、UDP长度加UDP头部加数据如果校验失败很多网卡和操作系统IP栈会直接丢包但发送端看起来毫无异常——因为它根本不关心对端是否收到。这也是实测中对端“收不到包”的一个隐性原因建议在协议栈里加一个CRC/UDP校验错误计数器调测时先确认计数为0再去查其他环节。3. 移植过程从工程骨架到上板文件3.1 搭建Vivado工程和时钟约束这个环节的核心目的是把手上的开源文件和板卡BSP结合成一个干净整洁的工程。我先新建了一个空白的Vivado 2022.2工程目标芯片选择XCVU9P-FLGA2104-2L-E然后按以下顺序添加文件先是CMAC IP核Vivado的Catalog里直接搜“CMAC”就能找到UltraScale Ethernet 100G CMAC IP生成时选择带AXI4-Stream接口的模式。然后把verilog-ethernet仓库里的rtl目录下相关文件添加进工程包括mac、ip、udp、arp这些子目录下的模块。最后写一个顶层wrapper实例化CMAC IP和UDP协议栈中间插上我们的FIFO做跨时钟域缓冲。时钟约束是这里最容易出问题的地方。CMAC IP会根据配置自动创建几个时钟比如gt_rx_clk、gt_tx_clk、axi_lite_clk等Vivado会自动推断很多主时钟约束但你必须在XDC里明确约束用户侧逻辑时钟。我遇到的第一个时序问题是用户逻辑跑了322MHz但报告里总有约200ps的负slack后来定位到是跨时钟FIFO的读写指针逻辑没有做寄存分隔扇出太大。解决办法是把异步FIFO的读写指针各自用两级同步器打一拍并加set_max_delay约束避免路径过紧。3.2 用户接口适配把自定义流式接口改成AXI4-Stream开源UDP协议栈的对外接口在不同版本里略有区别但总体上都是围绕一个流式接口发送端有tdata、tkeep、tvalid、tlast、tuser接收端也一样。CMAC硬核的AXI4-Stream接口非常标准但数据位宽和信号命名可能有差异所以需要一个适配层把两者对齐。我按照下面的方式处理先把CMAC的s_axis_tx接口直接拉到顶层注意CMAC的tuser信号里有帧错误标记如果不需要可以忽略。协议栈内部的udp接口则通过一个宽度转换FIFO适配。因为协议栈的内部数据通路是64bit10G直出或者512bit100G直出你需要根据实际选型把位宽对齐。我用的是512bit通到CMAC所以协议栈内部给MAC层的数据接口也保持512bit避免每拍还要做位宽拼接的额外组合逻辑。特别要注意tkeep和tlast的配合。CMAC要求tlast拉高时tkeep必须有效表示最后一拍的字节数。很多新手自定义发送逻辑时数据长度不是整512bit对齐如果没把tkeep按字节掩码设正确CMAC会把多余的空字节当有效数据发出造成帧长错误CRC校验必挂无疑。整个适配层写完后我仿真验证了发送通路构造了64字节、128字节、512字节、1500字节四种长度的UDP包发送到协议栈后观察MAC输出帧的CRC字段和长度字段全部正确。仿真没过就直接上板大概率会浪费大量的调试时间。3.3 引脚约束和上板验证准备引脚约束这块我提前规划了两套约束一套用于纯逻辑测试把千兆/百兆以太网的引脚都约束到FMC扩展口上的百兆PHY芯片用于低速率验证协议栈的收发正确性另一套才是真正的100G QSFP28约束绑定到板卡背面的QSFP28连接器。上板前用Vivado Implementation跑一遍确认时序通过、IO error为零然后再生成bit文件。上板前我习惯先在ILA里抓几个关键信号比如tx_reset_done和rx_reset_done是否拉高CMAC的link_status、clock_data_lock是否正常协议栈收到的MAC帧计数器是否在设备发流前为0发流后大于0。如果这些都没问题再开始接PC上位机和iperf3做吞吐测试。实践证明提前留出ILA调试探针能节省2-3天的排错时间。4. 上板测试全流程从链路打通到线速打满4.1 测试平台搭建与预检测试环境我把FPGA板卡和一台带双口100G网卡的服务器用QSFP28 AOC线缆直连。服务器的网卡是Mellanox ConnectX-5双口100G系统是Ubuntu 22.04装好了mlx5驱动。注意网卡必须支持100G速率自适应且线缆长度不要太长短距离AOC线缆对上板调试最省心。上电后第一件事不是打流而是检查链路状态ethtool eth0 # 输出里看 Speed: 100000Mb/s, Link detected: yes如果链路没起来优先查两个点QSFP28模块的I2C是否正常识别CMAC的gt_reset_done是否拉高。我一开始用了一根光模块转AOC的转接线发现模块ID读不到链路一直是Down后来换了一根直连模块的AOC线才正常。链路OK后在FPGA侧查看CMAC状态寄存器确认接收的包计数在增加同时查看CRC错误计数器是否为0。如果CRC错误计数一直在涨大概率是FPGA和线缆之间的信号完整性问题这时候可以试着降低RS-FEC级别或者调整TX的预加重参数。这个现象在实验室用劣质线材时非常常见。4.2 用iperf3进行UDP打流测试链路通之后我用iperf3做了两组基础测试接收测试和回环测试。接收测试的思路是让服务器发出UDP流FPGA侧用ILA采样或者内部计数器统计收到的字节数对比服务器发送字节数看是否丢包iperf3 -c 192.168.1.10 -u -b 0 -t 30 -i 1 # -b 0 表示不限带宽UDP模式打满速 # 192.168.1.10 是FPGA内部分配的IP地址第一轮跑出来的结果很有意思测大包1472字节payload也就是1500字节的标准MTU时吞吐率达到98Gbps以上丢包率几乎为0。但换到64字节小包吞吐率直接掉到80Gbps都不到了PPS大约只有一百多万。这个结果完全符合预期因为小包场景下每包的处理开销固定而CMAC和协议栈的流水线处理能力是有限的。为了进一步确认大包线速我还用两个FPGA互打一块发、另一块收发现即使payload9000字节的巨型帧吞吐率也能稳定在98.8Gbps以上几乎逼近线速。这说明协议栈本身的处理能力足够瓶颈主要在于PC端网卡的驱动和中断合并。4.3 UDP回环测试与抓包验证打流测试之外我还单独做了一个UDP回环测试FPGA把收到的UDP payload原样发回给服务器。服务器端用一段简单的Python脚本或者socat监听9000端口发包后回收比对数据一致性。这里有一个非常关键的坑必须在FPGA侧把从MAC接收到的帧按源MAC地址回复而不是简单地从RX端口原样打回TX端口。如果直接回环交换机会学习到错误MAC转发表后面所有包都会走错。正确做法是协议栈在回复数据包时把MAC源地址填成自己的MAC目的地址填成接收帧的源MACIP地址同理。服务器端用tcpdump抓包验证回环内容tcpdump -i eth0 udp and port 9000 -vv确认IP地址、UDP端口、payload数据完全一致后说明整个收发通路已经完整打通。到这一步基本可以断定开源UDP栈在100G环境下不仅能用而且性能达标。4.4 压力测试连续打流24小时我不建议只跑一两分钟就宣布测试通过。高速网络协议栈在长时间运行下的稳定性远比瞬时吞吐重要内存泄漏、计数器溢出、状态机异常这几种问题都会在长时间运行后暴露。我的压力测试方案是24小时连续发流FPGA内部每30秒往板上DDR里写一次统计数据便于事后复盘。我打开了PC端的系统日志和FPGA的UART日志同时监测FPGA芯片温度看是否有因功耗过高导致的热漂移和时序劣化。整体跑下来24小时无丢包温度稳定在72°C左右DDR读写正常。这里要特别提示一点如果你在FPGA里做了流量统计一定要用64bit计数器。100G线速下每秒收发约3.5亿个64字节小包32bit计数器在不到1.2秒内就会溢出一次用32bit统计会发现数据“莫名其妙地回绕”。5. 常见问题与排查技巧实录整个移植和测试过程里我遇到了大概七八个比较典型的问题整理成了一张速查表按“现象→原因→解决方式”给出方便你到时候对照现象可能原因解决方式CMAC链路起不来link_status一直为0参考时钟频率不对、QSFP28模块未正确配置、光口无光模块检查GTY REFCLK频率、I2C读模块寄存器确认线速率、换AOC线FPGA收不到任何UDP包但RS-FEC和MAC层错误计数器不为0CMAC RX的data位宽和协议栈不一致或CRC校验在模块内已做但协议栈又算了一遍复位后统一配置数据位宽CRC字段如果是MAC层处理的就不需要用户再做ARP能通但UDP数据包全丢IP校验和、UDP校验和字节序不对PC端口被系统丢包抓包看checksum是否为0或正确临时关闭UDP校验和测试大包吞吐99Gbps小包只有80GbpsPC网卡中断合并、驱动吞吐上限调大网卡RX/RQ队列、开启larger socket buffer或者用FPGA对打一跑高吞吐就出现偶发丢包跨时钟FIFO过浅突发流量填满FIFO后被强制丢弃增加FIFO深度设置watermark启动背压信号UART打印的计数器值跳变不正常计数位宽不够32bit溢出改用64bit计数器且接时钟域隔离同步长时间运行后链路偶尔Down光模块热漂移长时间高功率下SerDes信号劣化加强散热检查光模块温度必要时降低RS-FEC等级iperf3客户端显示丢包但FPGA侧CRC错误为0PC网卡内部UDP接收缓冲区太小Linux下设置net.core.rmem_default和net.core.rmem_max为256MB5.1 小包PPS瓶颈是FPGA不行还是PC不行这是很多做高速网络的同学会面临的灵魂问题。100G以太网在64字节小包情况下的理论极值是148.8Mpps但PC端的Mellanox网卡在单队列接收时受限于PCIe带宽和中断处理实际能处理的PPS大概在20M-40M之间。也就是说如果你用PC打小包流量一定是PC先成为瓶颈。我在测试中发现当我把iperf3的小包流量调到极限时PC端CPU占用冲到了100%而FPGA内部的帧计数远低于线速。这说明瓶颈在PC发送端。为了验证FPGA本身能跑多快我改用FPGA对打一块板卡用简单的内部发包器构造64字节包往另一块板上发另一块板的计数器统计到约126Mpps这个数字基本说明协议栈本身是可以接近线速小包的。5.2 PC端UDP接收缓冲区调优如果你只用iperf3测试PC到FPGA的接收性能建议提前做两件事sysctl -w net.core.rmem_default268435456 sysctl -w net.core.rmem_max268435456Windows下也可以调整注册表里的UDP动态缓冲区上限否则在高速收到UDP数据时系统会因为缓冲区满而丢包但是显示上你只会看到模糊的“网络丢包”很难排查。调整后我测出的PC接收FPGA大包流量能达到99Gbps小包则大约在60Gbps左右受限于网卡驱动和协议栈处理。5.3 CRC字段的字节序是个隐藏大坑FPGA的CMAC硬核会负责以太网FCSCRC32的生成和校验所以这部分用户侧不需要操心。但如果你用的是10G/1G等需要用户自己计算CRC的模块那就必须注意以太网FCS的字节序在网络上是“反转后发送”的和你在FIFO里的字节顺序可能正好相反。常见错误是把计算好的CRC按顺序填进帧尾导致对方设备看到CRC错误直接丢包。开源协议栈里通常已经做了正确顺序的处理但在端口适配时容易把这层封装不小心绕过问题表现就是抓包工具能看到完整帧但对方网卡就是不收。6. 实测数据汇总与后续扩展思路测试全部完成后我把数据整理成一张汇总表测试项目包大小吞吐率丢包率备注FPGA接收PC发送1472B98.6Gbps0%PC端iperf3发送FPGA接收PC发送64B61Gbps0.02%PC端PPS受限FPGA回环1472B98.4Gbps0%收发一体FPGA对打9000B98.9Gbps0%双FPGA直连FPGA对打64B96.1Gbps约126Mpps0%接近线速小包从结果看开源UDP协议栈在100G环境下是完全可用的只要做好时钟收敛、字节序和FIFO深度设计线速收发不成问题。后续如果有充足时间我准备在这个基础上做两件扩展第一是把接收到的UDP流直接透传到DDR4通过PCIe DMA搬移到上位机这样就能变成一个真正的100G高速数据采集卡第二是加入多队列和RSS哈希让PC端多核能并行处理小包把小包PPS再往上推一推。我个人在整轮移植中最深刻的体会是高速网络项目能不能顺利跑起来拼的其实不是写逻辑的能力而是对时钟、复位、字节序、跨时钟域这些基本功的理解。把这四样做好开源代码一旦和环境接好剩下的上板测试就是水到渠成的事。希望这份记录能帮你少踩几个我踩过的坑。
返回列表