尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

FPGA实现100G UDP协议栈:从CMAC到AXI-Stream的完整实践

FPGA实现100G UDP协议栈:从CMAC到AXI-Stream的完整实践 第一次把100G光口点亮的时候说实话我手是有点抖的。四路25.78125G的差分对一根QSFP28 DAC线接到服务器网卡CMAC状态寄存器里的link_up从0翻成1那种感觉跟当年第一次点亮LED完全不一样。这篇文章记录的是最近做的一个实践把开源的100G UDP方案移植到手里的UltraScale开发板并完整跑通上板测试。项目本身不复杂难的是把CMAC、GTY、异步时钟、AXI-Stream这条链路的每一层都打通。如果你正在搞100G网络、FPGA高速接口或者打算在自己板卡上把UDP快速跑起来这篇东西应该能帮你省不少时间。这类移植工作最怕的不是代码量大而是“看起来通了、实际数据是错的”。所以我在整个过程中花的精力大头在自环、抓包、寄存器回读这些验证手段上。下面按整体设计、开源方案选型、平台移植、UDP协议栈实现、上板测试、故障排查的顺序来讲部分细节会写得比较细你可以直接当操作手册来用。1. 项目定位与整体架构拆解1.1 100G UDP在FPGA上到底解决什么问题先说需求来源。FPGA做100G UDP最常见的几个场景是高速数据采集与回放、网络流量生成与分析、数据中心里的硬件加速板卡。举个例子ADC采集卡一秒钟能吐几十G字节数据PCIe带宽不够用或者对方机器不在同一台服务器上那就得用网络把数据搬走。TCP在硬件上实现代价高UDP无连接、无状态、头部简单天然适合用FPGA做线速收发所以工程上普遍选择UDP作为承载协议。那为什么用100G而不是25G或者10G因为当数据产生速率逼近带宽上限时慢速接口就是瓶颈。100G以太网线速率是103.125GbpsMAC层有效数据速率约为100Gbps对应到用户侧的AXI-Stream接口典型配置是512bit位宽、约322MHz时钟每拍能挪64字节这个吞吐量足够应付绝大多数高速数据源。这个项目适合谁参考一类是像我这样要在自研板卡上做网络接口的FPGA工程师另一类是刚接触高速SerDes和网络协议栈、想找一个“能跑通”的参考路径来入门的人。你不需要从零写MAC和PCS那是Xilinx CMAC硬核的事你要做的是把它暴露出来的AXI-Stream接口管明白再把UDP/IP组帧逻辑写对。1.2 系统架构从物理层到用户逻辑的层次划分我习惯把整个链路分成四层来看分清楚之后移植的时候就不会乱物理层QSFP28光模块或DAC线缆把100G信号拆成4路25.78125G高速差分对进FPGA的GTY收发器。PCS/PMA层GTY负责串并转换CMAC硬核内部完成64B/66B编码、加扰、FECRS-FEC等对外暴露一个相对干净的用户接口。MAC层同样是CMAC的一部分负责Ethernet帧的定界、CRC32校验/生成、流控、VLAN标签处理等最终以AXI-Stream总线形式和用户逻辑交互。用户逻辑层我们移植的开源代码、自己写的UDP/IP组帧解帧模块、FIFO缓存、DDR搬运等。用文本画就是QSFP28/DAC - GTY SerDes (PMA) - PCS 64B/66B FEC - CMAC MAC - AXI-Stream - UDP/IP逻辑 - 用户FIFO/互联这么分层最大的好处是每一层都有关键的状态寄存器可以确认。物理层看GTY的QPLL Lock、电源good信号PCS层看PCS rx_link_statusMAC层看CMAC的tx/rx_statistics或link up状态用户逻辑层则是我们自己的计数器。一旦上板出问题就顺着这条链一层层往上查效率会高很多。1.3 为什么选择移植开源方案而不是完全自研自研一套100G以太网MAC是不现实的PCS和FEC复杂度极高时序又紧除非你是做芯片的不然没有必要。Xilinx的Integrated 100G Ethernet Subsystem IP已经把GTY收发器、PCS、MAC全做进去了用户拿到的是一个相对完整的参考设计里面还带着一个Ethernet Traffic GeneratorETM可以做自测。我们真正要移植和开发的部分其实很小把ETM换成自己的UDP协议栈或者把参考设计里的AXI-Stream接口接到自研的组帧解帧模块上。开源方案里比较主流的有verilog-ethernet、Corundum以及直接基于CMAC Example Design改造。选型的关键指标是文档完整度、代码可读性、对100G的支持程度以及和你手里板卡的匹配度。这部分我在下一节具体展开。2. 开源方案选型与代码结构解析2.1 三套主流开源方案对比我把接触过的三套方案列了个表方便你根据自己情况选方案特点适用场景踩坑提醒Xilinx CMAC Example Design官方提供完整的PCS/PMA/MAC和ETM自测环可直接上板跑通回环快速验证板卡硬件、作为基础框架二次开发ETM只是回环演示不能直接当UDP用需要替换verilog-ethernetAlex Forencich模块化支持10G/25G/100G代码风格统一有AXI-Stream接口组件想逐个模块理解并使用需要自定义协议栈作者主力验证在10G/25G100G部分需要自行适配时序Corundum开源PCIe100G NIC实现功能完整包含DMA、PCIe硬核、MAC想做完整网卡、深入学习100G数据通路项目庞大学习曲线陡移植到自己板卡工作量大我这次选的是Xilinx CMAC Example Design作为地基原因很简单手里的VCU118板卡有现成的QSFP28Xilinx官方工程和板卡匹配度最高先把链路跑通再把ETM替换成自研UDP模块风险最小。如果你拿的是其他品牌FPGA或者非Xilinx板卡思路是一样的只是硬核IP名称和寄存器接口不同。2.2 移植前必须读懂的工程结构打开CMAC Example Design之后顶层是example_top里面会例化这几个关键模块cmac_0Integrated 100G Ethernet Subsystem IP核包含GTY移植、PCS、MAC。axi_lite_clock_converter跨时钟域的寄存器配置通路将用户逻辑的AXI-Lite时钟转换为CMAC内部寄存器时钟。example_design_reset上电复位控制负责按顺序释放GTY reset、PCS reset、MAC reset。eth_traffic_generatorETM发送测试数据帧的模块也是我们主要替换对象。eth_traffic_monitorETM接收侧统计收到帧数量、CRC状态。顶层的关键信号一般有gt_rxp_in/gt_rxn_inGTY差分输入、gt_ref_clk_p/gt_ref_clk_nGTY参考时钟、s_axi_*寄存器管理接口、axis_tx_tdata/axis_tx_tkeep等用户数据接口。上板前我建议花半小时把顶层信号表完整看一遍尤其是所有“输入”方向信号因为大部分移植问题都出在“该连的信号没连上、连上的信号方向不对”。2.3 数据位宽与AXI-Stream总线理解CMAC的用户数据接口是标准AXI-Stream100G配置下位宽很吓人tdata有512bit、tkeep有64bit。不是每个时钟周期都传输64字节tvalid和tready同时拉高才代表一个有效拍。tlast拉高表示当前拍是一个帧的结尾。tkeep则告诉我们这一拍64字节中哪些字节有效帧结束时往往不是整64字节。这里有个特别容易忽略的点tuser。CMAC的tx方向tuser可以携带用户自定义的帧信息比如是否带VLAN、是否计算CRCrx方向tuser携带错误指示。很多人在FPGA侧抓到的数据看起来对但上位机解析就是报错就是因为tuser配置错误导致MAC把整个包做了CRC替换或者长度截断。我在移植时直接把tuser按Example Design默认逻辑接好不轻易改先把功能跑通再优化。从64bit逻辑切到512bit总线时最容易出的问题就是字节偏移。我曾经在宽度转换FIFO里把数据按64bit对齐结果发出一个包后下一个包的起始字节错位Wireshark里看就是长度字段全乱了。解决方法是先明确帧的第一个字节在512bit总线中的位置再用tkeep做有效字节判定不要默认每拍都是满64字节。3. 平台移植实操从代码到上板3.1 硬件平台准备与检查清单动手移植前先花半天把硬件确认清楚能省掉后面很多返工时间。我这次用的平台情况如下主控FPGAXilinx UltraScale系列带GTY高速收发器。100G接口QSFP28端口支持4路25G。光模块/线缆短距离用QSFP28 DAC线缆长距离用SR4光模块加MPO光纤。服务器侧网卡Mellanox ConnectX-5 100G网卡用于互测。上电后的硬件检查顺序是先看电源指示灯确认12V供电没问题再看时钟芯片输出测SI5328或可编程时钟的参考频率是否稳定在156.25MHz第三步才是下载bitstream。很多GTY不上电、QPLL锁不住的问题回溯到最后是参考时钟起振失败或者时钟幅值不达标。一个必须要做的检查是QSFP28端口的引脚方向。有些板卡的LPMODE、Reset、ModPrsL引脚需要外部上拉或下拉错误配置会导致模块不识别。我在VCU118上遇到过插上模块但I2C读不到EEPROM的问题最后发现是某个板卡跳线帽没插到位模块的ModPrsL一直为高。这种硬件问题代码再怎么写都突破不了。3.2 时钟与复位最容易翻车的地方100G CMAC的时钟关系比较特殊我建议用表格理清时钟来源典型频率作用GT refclk板载可编程时钟156.25MHzGTY收发的参考时钟QPLL锁定依据tx/rx core_clkCMAC内部生成约322.265625MHzPCS与MAC间数据时钟axi_aclk用户逻辑时钟用户自行提供AXI-Lite寄存器配置通路axis_aclk用户逻辑时钟与用户逻辑同域AXI-Stream数据通路带宽要满足100G用户逻辑的axis_aclk不是随便设的。512bit总线要在322MHz左右的频率下工作才能覆盖100G线速如果用户逻辑跑不到这么高就必须在进入CMAC前安排异步FIFO或CDC同步并通过增加内部位宽、降低频率的方式折中。我在设计里用了512bit、250MHz的异步FIFO实测有效吞吐大概能到96G配合帧间隙和协议开销基本够用。复位时序同样关键。CMAC内部GTY和PCS的复位顺序严格官方example_design_reset模块已经处理好最好不要自己另写一套。移植时我犯过错为了图省事把所有复位都用引脚强制拉低结果GTY的QPLL一直不Lock折腾了两天才发现是复位没排完。后来老老实实把example_design_reset信号接到顶层问题立刻消失。3.3 引脚约束与XDC适配移植的很大一部分工作量在XDC约束上。打开官方example design的XDC你会发现里面包含大量板级引脚约束和时序约束。换到自己板卡时主要做三件事一是把物理引脚换成自己板卡的。包括GTY的gt_rxp/gt_rxn参考时钟gt_ref_clk_p/n以及QSFP28的控制引脚如modprsnt_l、lpmode、tx_disable等。IO标准、差分电压、LVDS等参数以你板卡原理图和厂商模板为准。二是保留CMAC IP自动生成的时序约束。这些约束藏在IP输出的xdc里比如GTY的时钟组、异步时钟组等千万别删。我在适配时曾经为了“清理干净的xdc”删掉这些约束结果时序收敛完全乱掉跑上板就是偶发错包。三是加自己的异常路径约束。异步FIFO两端的时钟是异步的需要在xdc里显式声明set_clock_groups -asynchronous否则Vivado会报一堆hold violation甚至强行插寄存器影响逻辑。上板测试时出现的随机丢包、偶发CRC错误很多时候就是这些异步路径没有约束好导致的亚稳态积累。3.4 用户逻辑替换ETM的对接方法Example Design里ETM的输出信号其实就是标准的AXI-Stream所以替换逻辑不算复杂。我把ETM从例化列表中摘掉把自研UDP栈的TX/RX接口接到原ETM连接的信号上。需要注意的接口名基本是这些CMAC侧信号方向说明axis_tx_tdata[511:0]输出用户发送数据总线axis_tx_tkeep[63:0]输出发送字节有效指示axis_tx_tvalid输出数据有效axis_tx_tready输入CMAC反压信号axis_tx_tlast输出帧结束axis_tx_tuser输出用户控制标记axis_rx_tdata[511:0]输入接收数据总线axis_rx_tvalid/tready/tlast/tuser输入/输出接收控制信号如果你是第一次接这种高速总线建议先写一个最简单的“伪UDP”模块固定把几个字节持续发出去比如发MAC头、IP头、UDP头然后跟递增计数。先验证链路能通、上位机能收到对的数据再往上加真正的业务逻辑别一上来就接DDR搬运和大FIFO出了错连定位点都没有。4. UDP协议栈设计与帧格式细节4.1 TX方向组帧流程从用户数据到以太网发送用户数据进入UDP协议栈后TX方向需要依次拼接以太网头、IP头、UDP头、用户载荷。这个拼接过程我建议用一种简单的状态机来做而不是在FIFO里倒腾数据逻辑清晰也容易加CRC。状态机大概长这样IDLE - 等待用户数据有效 - ETH_HDR: 输出目的MAC、源MAC、EtherType0x0800 - IP_HDR: 输出IPv4头版本/IHL/总长度/协议/校验和字段 - UDP_HDR: 输出源端口、目的端口、UDP长度 - PAYLOAD: 原样输出用户数据 - FCS: 使能CMAC硬件自动添加CRC32MAC地址字段需要自己小心处理。目的MAC如果是FF:FF:FF:FF:FF:FF就是广播帧接收端不需要精确匹配如果要发给特定服务器必须填服务器网卡的MAC地址。源MAC填写FPGA板卡的MAC注意不要和你局域网上别的设备重复。我在测试环境里人工分配了一个“AA:BB:CC:DD:EE:01”的私有MAC避免和现有设备冲突。IP头里总长度字段非常重要它等于20字节IP头 8字节UDP头 UDP载荷长度。UDP长度字段同样等于8字节UDP头 UDP载荷长度。很多第一次写的人只改了UDP载荷忘了同步改IP总长度结果上位机要么收不到包、要么包长错误导致丢弃。4.2 IP/UDP校验和硬件计算思路IP头校验和是必算的算法很简单把IP头按16bit为一组做反码和Ones Complement Sum再将结果取反。硬件上用组合逻辑累加IP头若干字再补一个16bit加法即可。我给出一个简化Verilog片段思路// 假设ip_header_words为5个字IHL5不含Option sum ip_header_words[0] ip_header_words[1] ... ip_header_words[4]; checksum ~(sum[15:0] sum[31:16]); // 如果需要进位叠加这里有个小技巧校验和的计算可以提前一周期在输出IP头时就把checksum字段放到总线上不必等整个包算完再补填省一个拍。用CMAC的tuser信号可以跳过某些帧的CRC重算但IP校验和还是要自己算。UDP校验和相对宽松IPv4协议下UDP校验和允许为0表示不需要校验。很多FPGA UDP实现为了省事填0上位机也不会拒绝。但如果你做的是IPv6 UDP或者对端对校验有要求就必须按“伪头部UDP头载荷”来计算开销会大不少。我这版只支持IPv4所以UDP校验和置0实测互通没有任何问题。这里有一个容易踩的坑即使UDP校验和填0也必须保证IP头校验和正确否则Linux协议栈会直接把包丢掉你在应用层什么都收不到。我最早测试时IP头校验和算错wireshark能看到包但应用收不到浪费了小半天排查。4.3 RX方向解帧流程过滤、剥离、提数RX方向的逻辑相对简单但过滤条件必须先想清楚。CMAC默认可能有MAC地址过滤、VLAN过滤等机制如果不知道设置很可能你的UDP逻辑根本收不到非广播包。我建议第一版先开启混杂模式让所有帧都进用户逻辑然后自己按偏移地址判定该不该收。软件逻辑上的解析流程是接收帧 - 判断MAC头目的地址可选 - 判断EtherType0x0800 - 判断IP头版本0x45、协议字段17UDP - 判断目的端口是否匹配 - 剥离EthernetIPUDP头输出载荷长度判断要严谨。如果IP头里的总长度字段和实际接收到的AXI-Stream包长不一致说明对端发错了应该丢弃。我还在RX侧布了一个error计数寄存器专门记录“长度错误”“IP版本错误”“非UDP协议”三个计数上板测试时通过AXI-Lite回读非常有用。4.4 性能优化点FIFO深度、反压与吞吐UDP收发性能上不去绝大多数情况不是协议栈逻辑问题而是FIFO和反压没处理好。TX方向如果用户业务逻辑比网络侧快FIFO会满此时必须拉低tready或者暂停上游数据写入否则就是丢包。RX方向如果下游DDR带宽不足FIFO满后只能让tvalid保持高、tready不拉高靠反压让CMAC侧自然降低进入速率但这样可能造成MAC侧的溢出丢包所以FIFO深度要按最大突发计算。我算过一组参考值100G线速下1us能产生约12.5KB数据。如果用户逻辑处理DDR突发需要2us那FIFO至少要有25KB容量才能避免丢包实际留1.5倍余量比较稳。BRAM做25KB FIFO不算大但如果缓存深度需求上到几百KB就得切URAM或者直接写DDR这也是为什么很多100G设计里数据通路会接DDR。5. 上板测试流程与性能验证5.1 测试环境与连接拓扑硬件环境搭起来很直接FPGA开发板 QSFP28口 |----- QSFP28 DAC/AOC线缆 -----| 服务器100G网卡 |----- 服务器上配置IP、安装iperf3/wireshark -----|IP规划建议FPGA固定一个静态IP比如192.168.10.2/24服务器网卡设置192.168.10.1/24。注意如果服务器网卡启用了DHCP可能自动获取到别的网段先把它设成静态避免路由跳转导致抓包困难。工具准备方面iperf3必装它可以打UDP流也能统计丢包率和抖动Wireshark必装用来查看报文格式和校验和字段如果你的服务器端能装Ostinato或者Scapy做更细粒度的发包控制会更方便。FPGA侧用ILA抓AXI-Stream总线同时用AXI-Lite小工具回读统计寄存器。上板之前还有一件容易忽略的事情确认板卡和服务器网卡的协商模式一致。100G以太网一般不需要自动协商速率两端都固定100G。若有一端因模块识别问题降到40G或25G链路状态就非常诡异看起来link up但带宽上不去。5.2 先过自环不信任任何一根线缆拿到板卡后第一步不是直接连服务器而是做自环测试。自环分成三档GT收发器内环PMA Loopback把GTY接收端和发送端在SerDes内部短接测试收发通道的数字逻辑和物理层是否正常。CMAC近端环回Near-End Loopback把TX数据在MAC内部绕回RX路径验证UDP逻辑到MAC层的对接没问题。外部物理环回用一根DAC线或光模块自环转接头把FPGA的TX口直接连到同一个QSFP28的RX口验证物理信号完整性和CDR恢复能力。我最推荐第三档因为前两档绕过了真实物理链路很多噪声、眼图、CDR问题暴露不出来。实测中我发现PMA内环全通、外部环回却经常掉link原因就是DAC线缆质量不行或者接触不良。当自环完全没问题之后再连服务器网卡问号会少很多。自环测试时的重点是看PCS同步是否锁定、CRC错误计数是否为零。如果自环就疯狂报CRC错基本可以断定GTY通道配置、参考时钟或电源纹波有问题能查的地方比较少得先用IBERT做误码率测试。5.3 与服务器网卡互通iperf3打流实测自环通过后开始和服务器网卡对接。最简单的验证不是pingFPGA没做ICMP而是直接UDP收发。第一步服务器端起iperf3服务端iperf3 -s第二步如果你有一个简单的FPGA发包模块可以让FPGA周期性地发UDP包服务器端用Wireshark抓包确认格式。或者反过来服务器端用iperf3打流到FPGAiperf3 -u -c 192.168.10.2 -b 10G -l 1400 -t 30这条命令意思是以UDP模式发送到FPGA目标带宽10Gbps负载大小1400字节持续30秒。FPGA侧只要统计收到的UDP包数量通过寄存器回读出来和iperf3的发送量对比就能算出丢包率。测FPGA发送方向也一样FPGA连续发数据后服务器端看iperf3的接收统计。我这次实测结果不加FEC时FPGA TX峰值约98GbpsRX方向配合大FIFO能到96Gbps继续往上推就开始丢包。这不是CMAC的锅而是我的用户逻辑时钟只有250MHz、以及FIFO延迟带来的反压所致。如果你优化的目标是榨干线速建议把用户时钟提到322MHz以上并把关键路径流水化不要让tready出现频繁拉低。5.4 用Wireshark和寄存器校验数据正确性光看带宽达标还不够必须确认收到的数据内容是准确的。方法是在FPGA发送的UDP载荷里放一个递增计数器上位机抓包后检查计数值是否连续或者上位机发送固定序列FPGA侧做逐字节比对并统计错误次数。Wireshark里需要重点看三处IP头校验和状态如果显示incorrect说明FPGA发的IP校验和算错了。UDP长度和载荷长度是否匹配。帧间的序列号是否有跳变跳变说明有丢包。FPGA侧我建议提前在目标板上保留一组可读寄存器比如收到UDP包总数、CRC错误数、长度错误数、非UDP协议数。回读这些计数器比看ILA信号要直观得多。因为ILA抓512bit总线一次只能抓几千个周期看不了全局趋势而计数器可以反映整个测试过程的累计情况。6. 高频故障排查与调试心得6.1 故障速查表这套流程跑下来我把最常见的坑整理成了一张表你在自己环境里遇到类似现象可以直接对号入座现象可能原因排查方向GTY QPLL一直不Lock参考时钟频率不对、电源不稳、复位未释放万用表/示波器查参考时钟检查复位时序link up但RX零包MAC地址过滤、VLAN过滤、CRC错误被丢弃开启混杂模式读CMAC统计寄存器ILA抓RXWireshark能看到包但应用收不到IP校验和错误、UDP端口不对、分片重组失败看wireshark的checksum提示逐字段对照能收能发但吞吐上不去用户时钟太低、FIFO深度不足、tready频繁拉低统计总线有效利用率加大FIFO或提升时钟自环正常但连服务器丢包光模块/DAC质量问题、网卡固件/驱动协商异常换线缆检查网卡lspci/ethtool协商速率偶发错包、时序收敛困难异步时钟约束缺失、复位释放亚稳态检查xdc的clock group约束复位信号做同步释放6.2 ILA抓AXI-Stream总线的几个实操经验用ILA调试512bit总线首先要解决的是存储深度问题。512bit数据一次抓2048拍就是128KB很快就把BRAM耗尽。我的建议是不要长时间抓数据而是设置触发条件抓关键事件比如触发条件axis_rx_tvalid axis_rx_tlast抓到帧尾这一拍就能通过tkeep判断有效字节数通过tdata看到最后几个字节的内容用来核对包长是否正确。如果怀疑特定帧出错可以加一个条件比较器比如计数到特定序列号再触发。ILA还有一个坑触发条件里不能方便地表达“两个时钟沿之间”的延迟。跨时钟域时你看到的总线数据和用户逻辑实际写入的数据可能相差一拍。因此任何基于ILA的“眼见为实”都要和仿真波形对照才靠谱。我的习惯是同一段逻辑先在Vivado Simulator里跑几百个周期的仿真确认行为正确再上板用ILA抽查。6.3 从10G/25G到100G的思维转换最后说点掏心窝子的话。如果你之前做过10G或者25G以太网会发现100G最大的区别不是“更快一点”而是所有东西都要并行化。以前10G网口用户数据位宽64bit、时钟156MHz逻辑怎么排都无压力到了100G数据位宽直接拉到512bit一个周期要处理64字节写RTL时思维必须从字节流水线切换成“拍级流水线”。举个典型例子在10G里你可以用一句if判断当前字节偏移是否等于某个值但在512bit总线里一拍的64字节里可能同时包含帧头、IP头、UDP头你必须用slicing方式把它们一次性切出来没有第二个时钟周期让你慢慢选。这也是为什么很多100G开源代码看起来“花里胡哨”密集的case和assign其实都是在做位置固定的字节切片。如果你从没用过这么宽的总线建议先在仿真里构造几个边界用例帧长度正好对齐64字节、帧长度不足64字节、每个周期跨越两帧甚至三帧边界、tkeep多种组合。把这些跑通再上板才心里有底。我在这个项目里吃过最大的亏就是只仿真了“整帧连续发送”的理想情况结果实际操作中一旦遇到多帧拼接组帧状态机就乱了后来补了两个周期切多帧的处理才彻底稳定下来。移植100G UDP真正难的不是UDP协议本身而是把GTY、CMAC、异步时钟、AXI-Stream这条链路一层一层打通的心智过程。我踩过最大的坑是“看起来没有错实际上每个环节都有大量细节需要确认”。如果你也正在做类似的事别急着追求线速先保证你能用Wireshark看到一个干净的、校验和正确的包再谈性能优化。链路起来了之后后面的路就好走多了。
返回列表