FPGA实现100G UDP协议栈:从CMAC配置到上板测试全解析

发布时间:2026/9/7 15:32:55
FPGA实现100G UDP协议栈:从CMAC配置到上板测试全解析 前几天把一个开源的100G UDP协议栈从官方参考板卡移植到自己画的一块FPGA板上跑通了上板测试。这里说的100G是指物理层走QSFP28光模块或铜缆MAC层跑满100Gbps用户逻辑通过AXI-Stream直接收UDP帧全程不经过CPU。很多人一听100G就发怵实际上项目本身没那么玄核心就三件事把CMAC配置好、把UDP帧格式处理好、把复位和时钟弄对。这篇文章把这次移植上板测试的完整过程、踩坑记录、以及几个关键的数据通路设计思路都整理出来给准备入坑100G FPGA开发的同学做个参考。如果你之前做过10G或者25G的UDP收发那这次的核心经验完全可以平移数据位宽变大、时钟频率提高但帧结构、校验和算法、FIFO管理这些底层逻辑几乎没有变化。如果完全没接触过FPGA以太网也别慌我会把IP选型、时序约束、环回测试这些步骤拆开讲按顺序走完基本就能点亮。1. 项目背景100G UDP到底解决什么问题1.1 为什么需要FPGA硬件协议栈先聊聊为什么要做这件事。传统服务器上用软件协议栈收发UDPCPU开销非常大。一般单核跑到几Gbps就会占用很多CPU资源即便用DPDK这类用户态方案小包场景下也会被PCIe带宽和CPU频率限制死。但是在数据中心、网络测试仪、高性能采集系统这类场景里经常需要把多个100G口的数据直接转发、过滤、或者按特定格式打包这时候FPGA硬件协议栈就派上用场了。硬件UDP栈在整个链路里的位置是光模块/铜缆 → CMAC100G Ethernet MAC硬核→ UDP解析/组帧逻辑 → 用户FIFO或DMA。CMAC负责物理层和链路层的编解码用户逻辑只需要关心“从包头里取哪些字段”和“怎么把payload送出去”。我这次移植的目标很明确把开源工程里的CMAC封装和UDP逻辑从官方型号的板卡搬到一块自研的UltraScale板卡上验证光口能通、UDP能收能发、带宽接近线速。测试工具就是服务器上的iperf3和pktgen没有再单独写上位机。1.2 开源方案如何选型用哪套开源代码是移植前必须想清楚的问题。我了解到的方案主要有三个方向Xilinx 100G Ethernet Subsystem硬核CMAC加自己写UDP逻辑。这个方案最灵活CMAC是官方IP稳定性有保障UDP解析和组帧部分自己用FSM写代码量不大。Alex Forencich的verilog-ethernet开源库。里面包含了10G/25G/100G的MAC封装、UDP收发、ARP等模块代码风格干净总线都是标准AXI-Stream拿来改很方便。Corundum开源NIC项目。这个项目更完整包含了PCIe DMA、UDP、MAC配置、驱动全套基本上是一个完整的100G网卡参考设计适合做高端网络设备或者科研项目。我这次选择的是第二个方向配合Xilinx CMAC。原因是目标板卡不需要PCIe DMA只需要把UDP数据直接送到用户逻辑verilog-ethernet里的udp_ip_rx和udp_ip_tx模块刚好够用。如果你的项目需要和上位机配合或者要做多队列、DMA那直接研究Corundum会更合适。提示开源的“UDP栈”很多在1G/10G上很成熟但100G的表达很少。实际上UDP逻辑本身和速率关系不大真正决定能不能上100G的是MAC层和AXI-Stream数据通路的宽度与时钟频率。所以选型时重点看MAC封装是否支持CMAC而不是只看UDP模块。1.3 移植前先评估的三个风险点移植不是把代码down下来直接综合耗时间的往往是下面三件事第一参考板卡和目标板卡的引脚约束完全不同。CMAC用的是高速GT引脚位置、参考时钟、供电都不一样所有XDC约束都得重新写。第二复位顺序。100G CMAC对复位时序非常敏感上电后要先等GT参考时钟锁定再往下拉复位之后还要监测tx/rx状态信号。顺序错了接口就是不通。第三时钟域设计。CMAC的AXI-Stream用户时钟是固定的比如100G一般用512bit位宽322.265625MHz。你的UDP逻辑、FIFO、计数器都必须在这个时钟域下工作跨时钟域要处理好。把这三点提前列出来后面上板调试能省一半时间。2. 100G UDP数据通路的核心设计思路2.1 AXI-Stream接口的位宽和时钟Xilinx 100G CMAC的AXI-Stream接口默认是512bit宽度用户时钟频率322.265625MHz。512bit等于64字节也就是每个时钟周期最多能处理64字节数据。你可能会奇怪接口带宽算下来是512×322M≈165Gbps为什么说这是100G MAC因为以太网帧之间还有前导码、帧间隙IPG等开销AXI接口并不是每拍都有效tvalid会有节奏地拉高拉低。实际平均有效带宽约100Gbps瞬时接口带宽大于线速是为了保证小包模式下数据不阻塞。这个细节在上板测试时很重要。如果你的UDP逻辑一拍只能处理一个包头64字节小包线速约1.488亿包/秒换算下来每个包平均只有约6.7ns也就是约两拍多一点的间隔。如果再在FSM里加一拍判断或者遇到等待FIFO满再暂停吞吐就会掉下来。我测试的结论是只要不是刻意追求极限小包性能普通的单拍状态机收发大包如1400字节跑到线速没问题但要做64字节小包打满就必须保证每拍都能处理完一个包头流水线设计不能有FIFO反压导致停拍。2.2 UDP收发模块的典型结构一个完整的UDP硬件收发模块大概分四块接收方向AXI-Stream端口进来先做以太网帧头解析目标MAC、源MAC、EtherType再解析IPv4头版本、协议号、源IP、目的IP、总长度最后解析UDP头源端口、目的端口、长度、校验和。只保留匹配指定IP和端口的payload。发送方向根据用户配置的源MAC、源IP、目的IP、目的MAC、目的端口把payload前插UDP头、IPv4头、以太网头组成完整帧发给CMAC。FCS校验可以直接让CMAC在发送时生成省去自己算CRC。控制寄存器用AXI-Lite提供若干寄存器比如本机IP、本机端口、目的IP、目的端口、统计计数收包数、发包数、CRC错误数。FIFO接口和用户逻辑对接的地方。接收方向把payload写入FIFO发送方向从FIFO读出payload组帧发出。用生活类比来解释接收方向就是拆快递CMAC送来的包裹以太网帧先拆掉外层快递袋以太网头再拆掉中间包装盒IP头最后把里面的商品UDP payload取出来。发送方向则是反过来的装箱流程。2.3 校验和计算的两种做法IPv4包头里有校验和字段UDP头里也有可选的校验和字段。硬件实现上IPv4包头校验基本都是实时计算的用16位逐位累加一轮迭代约需8个周期流水线化后不阻塞数据流。UDP校验和比较复杂因为要覆盖伪头、UDP头和payload在发送方向上必须等整个payload都到齐才能算出结果。实际工程里有两个选择保守做法UDP校验和填0。IPv4网络里UDP校验和为0表示未启用校验大多数网卡和协议栈能正常收发。缺点是极端场景下可能被网络设备丢弃建议仅用于板级验证。完整做法发送时先把payload缓存或预存到一个FIFO里边发边算校验和到最后一个数据时把计算完的校验和填进UDP头的checksum字段。因为UDP头的checksum位于payload之前所以要么预发送一次算出结果再正式发要么把checksum字段留出来最后回填。我在这次移植里用的是第一种简化方案UDP校验和填0IP校验和硬件实时算。板上对端用Mellanox ConnectX-6网卡iperf3和Wireshark都能正常收包。如果以后要接对校验敏感的设备再升级成完整校验逻辑就好。注意IPv4头里的校验和必须正确这关系对端是否会直接丢包。UDP校验和可以先置0跑通链路不必一开始就实现完整算法。3. 移植实操从参考工程到目标板卡3.1 引脚约束与时钟约束怎么改移植的第一件事就是重写XDC文件。参考工程里的GT引脚、参考时钟引脚、复位按键、LED这些物理定义全部要换成目标板卡的原理图实际连接。100G CMAC相关的约束主要有几类GT参考时钟引脚比如MGTREFCLK0_116需要例化IBUFDS_GTE4原语并在XDC中指定引脚位置。QSFP28模块的复位脚、低功耗模式脚、I2C引脚按板卡原理图分配普通IO。用户逻辑的时钟如果CMAC工作在用户时钟模式接收方向会恢复出rx_clk发送方向需要外部提供tx_clk。通常tx_clk可以直接用参考时钟生成或者由CMAC共享逻辑输出。复位信号建议不要直接绑到按键上而是通过VIO或者上电自动复位状态机控制。移植后的第一次综合重点检查时序报告里有没有“clock unconstrained”或“routing congestion”这类问题。CMAC的GT区域布局是固定的如果引脚分配和IP核的Location约束冲突布局布线会报错。3.2 CMAC复位时序与状态检查CMAC的复位是整个链路最容易出问题的地方。Xilinx 100G Ethernet Subsystem内部有复杂的复位状态机直接拉一个低电平复位然后不管端口大概率不通。比较稳妥的做法是参考官方Example Design里的复位逻辑上电后等待GT参考时钟稳定一般等待不少于10ms。按下复位后依次完成TX和RX的复位释放。观察两个关键状态信号tx_axis_tready是否拉高rx_axis_tvalid是否开始有数据以及stat_rx_block_lock等状态位。我这次在自研板卡上就遇到了一个典型问题参考时钟引脚没接对导致GT始终无法lockrx端口一直没有block lock。后来用VIO抓取CMAC状态寄存器发现gt_txusrclk没有输出才定位到是IBUFDS_GTE4控制信号的方向问题。一般建议在工程里预留一组VIO探针把CMAC的复位、状态、计数器都拉出来调试起来效率高很多。3.3 用户逻辑与CMAC的对接CMAC的发送端口是AXI-Stream主机接口需要外部逻辑保证以下几点tdata为512bit字节序遵循端到端的网络字节序通常从高字节或低字节开始取决于CSR配置。tkeep按实际有效字节拉高。比如一个1400字节的UDP包最后一个beat的tkeep不会是全1。tlast表示一帧结束tvalid和tready握手必须满足AXI-Stream规范。CMAC内部不能长时间停等如果上游FIFO要反压必须握好否则会丢帧。tuser在CMAC里用于传递错误标志和时间戳一般收发时按端口定义置0或按需使用。UDP发送模块和CMAC对接时最常见的坑是tkeep没处理好。很多第一次写的人以为tdata低位有效结果最后一个字节的位置错位对端抓包看到payload后边堆了垃圾数据。建议先在仿真里把64字节边界的拆包场景覆盖全再上板。3.4 与用户FIFO、DMA的数据通路设计如果只是自测UDP payload直接接内部FIFO是最简单的接收方向把解出来的payload写入FIFO发送方向从FIFO读出数据并组帧。FIFO的位宽建议和AXI-Stream一致512bit避免位宽转换的额外时钟周期。FIFO深度取决于你的数据量。如果你只是做“收到什么就回什么”Loopback测试深度1K或者4K都够。如果你要做大流量采集或者跨时钟域缓冲那就要根据“最大突发长度”计算深度并加一个almost_full阈值避免FIFO反压把数据堵死。如果未来要接PCIe DMA数据通路基本类似只是把FIFO换成DMA描述符引擎。Corundum项目里就有现成的实现可以对照学习。4. 上板测试从环回到真机打流4.1 测试环境搭建上板需要的硬件和工具如下目标FPGA板卡包含QSFP28接口。QSFP28 DAC线缆或者光模块加AOC线缆。DAC便宜短距离测试首选。对端设备一台带100G网卡的服务器我用的是Mellanox ConnectX-6或者另一块100G FPGA板卡。软件工具iperf3、pktgen-dpdk、Wireshark或者tcpdump。调试工具Vivado里的VIO和ILA用于观察内部信号。如果只有一块板卡也可以先做内部PCS环回不需要对端设备就能验证CMAC和UDP逻辑。Xilinx CMAC内部提供一个数字环回模式把TX数据在PCS层直接送回RX方向整个协议栈的数据通路都能跑通但不能验证光模块和物理信号。4.2 回环自测先确认协议栈逻辑正确建议第一次上板测试按这个顺序来配置CMAC开内部PCS loopback不插光模块。用VIO触发一次复位观察tx_axis_tready是否拉高。内部产生一个简单的测试报文比如连续递增数据从发送逻辑发出。在接收方向统计收到的包数和CRC错误数。如果计数递增且CRC错误为0说明UDP组帧、解析、CMAC链路都正常。这一步能排除大部分逻辑问题。只要回环测试通过协议栈本身的正确性基本有保障。然后用DAC线缆把FPGA板卡和服务器网卡直连关掉loopback再测一遍。如果直连后不通问题往往出在物理层或者服务器侧配置比如协商速率、网卡固件、IP配置。4.3 用iperf3验证UDP吞吐服务器端开启iperf3 UDP接收iperf3 -s -u -i 1FPGA端如果做的是静态回环或者自发自收简单一点的做法是内部生成一个流量源持续向服务器发送指定目的IP和端口的UDP包。服务器端看接收速率和丢包率。如果FPGA端只是接收不发送那就反过来用服务器端发流iperf3 -c 192.168.10.10 -u -b 20G -l 1400 -t 30注意perf3的单线程UDP在100G网卡上不一定能打满因为单核处理有限。通常会开多个流或者用pktgen-dpdk做高精度发包。一个常见问题是FPGA内部有计数器显示发了10Gbps但服务器只收到很小一部分。先检查服务器网卡有没有开流控、中断聚合、驱动参数其次检查线缆和光模块信号质量最后查FPGA侧发送FIFO是否溢出。4.4 用pktgen-dpdk做小包极限测试如果想验证64字节小包线速iperf3很难准确测因为CPU无法在小包场景下维持满吞吐。这时候建议用pktgen-dpdk。pktgen是DPDK生态里的高性能发包工具配置好网卡后可以精确设置包长、包数、发送速率还能统计收包和丢包。小包测试时关注两个指标发送端口是否达到线速接收端口丢包率是否为零。我测试中遇到过64字节包打满后FPGA侧RX方向统计收到的包数和服务器发出的包数不一致。排查后发现是UDP接收模块在处理tkeep不全的包时多等了一拍导致反压给CMAC产生丢包。后来调整了解析逻辑每拍都能完成整包头判断才解决。提示如果严格按AXI-Stream来做tlast一拍就代表整个包结束不管tkeep是满还是不满。解析状态机里不要在tlast后再加额外空闲判断否则小包场景必然掉速。4.5 常见问题排查速查表现象可能原因排查方法tx_axis_tready一直为低CMAC未使能或GT未锁定读CMAC状态寄存器查gt_txusrclk确认复位状态机完成rx一直无数据block lock丢失参考时钟问题或光模块没插好用VIO看已捕获时钟信号检查QSFP28的LPMode/Reset电平服务器端ping不通FPGAARP没有应答在服务器上静态配置ARP条目或者UDP栈加ARP模块收到大量CRC错误计数器在涨物理信号问题或对端FCS配置不一致换一根短DAC线缆检查对端网卡是否开启FCS offload1500字节大包线速正常64字节小包丢包解析逻辑存在每包多拍处理用ILA抓AXI-Stream观察tvalid/tlast间隔是否过大iperf3实测带宽远低于10GCPU瓶颈或iperf3单线程限制开多流或用pktgen-dpdk测线速确认不是FPGA侧堵5. 性能测试结果与瓶颈分析5.1 实测吞吐数据这次用自研板卡实测的结果如下报文长度发送速率接收速率丢包率64字节线速148.8 Mpps线速148.8 Mpps0%512字节线速线速0%1400字节线速线速0%这里需要说明64字节小包线速148.8Mpps是按“有效以太网帧前导IFG”的线速定义算出来的。FPGA实际内部逻辑每拍64字节理论上64字节包一秒需要约1.49亿拍而CMAC用户时钟是322MHz远高于这个速率因此余量充足只要处理逻辑不额外加拍就不会丢。实测下来真正限制链路速率的原因反而是服务器端。Mellanox网卡在64字节小包场景下如果用纯软件栈接收CPU会先成为瓶颈所以测试时我用了DPDK绑核才能稳定达到线速。5.2 协议栈内部逻辑的资源占用UDP协议栈逻辑本身资源占用不多。我这次用的用户逻辑大概只消耗了几千个LUT和FF主要资源都耗在CMAC和FIFO上。这是硬件协议栈相比软件方案的优势之一100G吞吐量的处理逻辑规模非常小可以在同一片FPGA里再塞大量数据处理逻辑。5.3 哪些环节最容易成为性能瓶颈三个地方需要重点关注。第一发送方向的FIFO。如果用户数据写入FIFO的速率小于发送速率FIFO会空转导致实际吞吐降下来。这里建议用FWFT或标准AXI-Stream FIFO并且留足深度。第二接收方向的解包状态机。如果状态机为了兼容某个奇怪字段多等了一个cycle小包吞吐就会下降。我的经验是妖不要一开始写太复杂先用固定IP和端口验证通路其次再扩展RARP、VLAN等。第三跨时钟域处理。如果payload最终要送到另一个时钟域比如DMA时钟位宽转换和同步FIFO的带宽不能低于UDP处理带宽。最好使用512位宽的异步FIFO避免位宽转换带来的吞吐损失。6. 移植过程中踩过的坑与技巧6.1 静态ARP绕过了“Ping不通”第一次直连测试时服务器一直ping不通FPGA的IP。查了一圈发现UDP栈里没有实现ARP应答逻辑。服务器发ARP请求找FPGA MACFPGA不回IP包自然发不出去。如果你只是要验证UDP链路最简单的办法是服务器上手动加一条ARP静态条目arp -s 192.168.10.10 aa:bb:cc:dd:ee:ff这样服务器就不发ARP请求了直接往FPGA的MAC地址发包。但不能解决应用层依赖Ping的问题。如果以后要做成产品最好在协议栈里补一个轻量ARP回复模块只有几十行代码收到ARP请求后按规则回一个响应帧就行。6.2 复位释放顺序的教训这次移植让我最花时间的是直连测试时TX方向一直没有数据发出。CMAC配置、引脚约束、光模块都查了最后用VIO抓寄存器才发现是因为复位信号释放时GT还没完全稳定CMAC内部状态机卡住了。后来采用的办法是把复位拉长到不少于100us并且分两步第一先等系统时钟和参考时钟稳定第二先释放GT复位等待gt_txusrclk有效后再释放MAC核心复位。Xilinx文档里这些时序其实都有但移植参考工程时抓住“寄存器和状态机复位不分离”这个点会减少大量调试时间。6.3 环回测试与直连测试的区别内部PCS loopback通过以后千万别急着接对端服务器。建议把DAC线缆插上先做外部物理层环回用一根QSFP28测试线绕过光模块借助板卡上的外部环回或者直接调整DAC成环回模式再测一次。这一步能确认差分信号口和PCB布线没有大问题。然后再连服务器。这样测试分段隔离出了问题能快速定位是FPGA侧还是物理链路还是服务器侧。6.4 ILA探针的使用建议100G用户时钟322MHzILA在这种高速时钟下的采样深度有限。我建议在调试时不要把ILA挂在CMAC的AXI-Stream总线上而是挂在UDP逻辑后端的计数器和状态信号上。这样能确认UDP层收到多少包、CRC错误有多少、FIFO满标志有没有拉高比盯着波形更高效。如果需要看特定类型的包可以在ILA的触发条件里加入tlast和tvalid、目的端口等条件组合。注意ILA的存储深度很小触发窗口内可能只能抓到几拍波形所以能采状态就采状态不要盲目抓原始数据流。7. 最后的几点个人体会这次移植测试从开始写XDC到最终线速通过前后大概用了一周时间。中间大部分时间不是耗在UDP逻辑上而是耗在“复位没放对、参考时钟没约束、服务器驱动没调优”这些看起来很小但很致命的问题上。我个人体会最深的一点是做100G UDP这种项目一定要先把测试方法和调试工具准备好再动代码。VIO、ILA、计数器、串口打印、上位机脚本这些比具体协议逻辑更能决定项目进度。另外别急着追求一步到位先把10G或环回模式调通再看100G直连效率反而高。如果你正在纠结要不要上100G我给你一个直接的判断标准如果只是学习UDP协议栈原理10G完全够用如果是要处理真实的高速数据流那100G没有想象中可怕硬件栈逻辑复杂度并没有比10G高太多难点全在物理层和时序约束上。按照“环回→直连→软件打流”的顺序按部就班测一遍基本都能跑通。