FPGA 100G UDP上板测试实战:从CMAC架构到iperf3打流
2026/9/11 20:57:43 网站建设 项目流程

做FPGA高速接口这些年,有个现象很典型:100G Ethernet设计,仿真里跑得风生水起,AXI4-Stream波形一帧接一帧,校验和也算得完美,结果一上板,要么link起不来,要么回环一打流就是满屏的CRC Error。调试器一抓,全是稀碎的FCS校验错误,然后就开始怀疑时钟、怀疑约束、怀疑人生。

我这次要聊的就是这个事:怎么用一套开源方案,在FPGA上把100G UDP链路搭起来,并且完成真实的上板打流测试。注意,不是“仿真通过”,也不是“接口通了”,而是真正用服务器网卡打流量,测吞吐、看丢包、长时间跑稳定性。100G UDP上板测试的关键从来不在于“能不能通”,而在于“稳不稳”——能不能线速转发不掉包,能不能连续跑十几个小时不积累错误,能不能拿到可信的吞吐率和丢包率数据。

这篇内容适合正在做高速网络接口、数据中心流量监控、高速数据采集或者网络测试仪的FPGA工程师,也适合研究生阶段想拿“开源UDP协议栈”作为切入点、把FPGA高速收发链路吃透的同学。我会把我实际踩过的坑、验证过的路径和最终的测试方法都摊开讲,希望能帮你少走几个月弯路。

1. 项目背景与核心需求拆解

1.1 100G UDP在FPGA上的典型应用场景

先说清楚一个事:为什么要在FPGA里做100G UDP,而不是直接上商用网卡?

商用100G网卡确实成熟,驱动也好用,但它的处理路径是固定的:报文进硬件队列,驱动轮询,协议栈处理,最后拷到用户态。这条路径的延迟在微秒量级,而且CPU占用高。FPGA做UDP offload,核心优势是延迟极低——从网口收到数据到应用逻辑拿到payload,可以控制在几百纳秒到几个微秒之间,而且完全不占CPU。

实际项目里我见过三类典型场景:

第一类是高速数据采集。比如射频前端采样、软件无线电、粒子物理实验的探测器数据,数据率动不动就是几十Gbps。这种数据没法全部塞给CPU,只能靠FPGA做实时预处理,再把浓缩后的结果通过UDP发出去。这里的UDP更像一个管道,不关心可靠性,只关心带宽和低延迟。

第二类是流量监控与镜像。数据中心交换机做端口镜像,把流量复制一份给分析设备,分析设备里的FPGA做报文头解析、流表匹配、流量统计。UDP在这里通常承担两个角色:一是接收远程配置和控制命令,二是把统计结果或采样报文封装成UDP发往上位机。

第三类是网络测试仪。用FPGA构造线速流量、模拟多用户并发、测量丢包和时延抖动。这类场景对UDP协议栈的要求是最苛刻的:不仅要发得出去,还得精确控制发包节奏、报文间隔、错误注入,甚至要能同时处理几十万条流。

在这些场景里,选UDP而不是TCP,理由非常直接。TCP是有连接协议,要维护序号、确认、重传、拥塞窗口,全速100G下这些状态的管理成本极高,别说FPGA了,CPU都费劲。UDP是无连接协议,头只有8字节,处理逻辑简单,查表转发可以做到每时钟周期处理一个包,天然适合硬件线速转发。

1.2 “上板测试”到底要测哪些东西

上板测试这个词看起来简单,实际上包含好几个层次,很多人理解不到位,一上来就直接对服务器打流,出了问题也不知道是哪一层挂了。

完整的100G上板测试,至少要从上到下拆成四个层面来验证。

第一层是物理层,也就是链路能否建立。100G以太网通常用4个25G通道并行传输(4x25G NRZ),每个通道经PCS编码后实际线速率是25.78125Gbps。如果物理层没锁住,上层什么都是白搭。这一层要观察PMA锁定、PCS对齐、RS-FEC同步状态,确认无误码或误码率极低。

第二层是MAC层,也就是帧能否正确成帧。以太网MAC负责生成前导码、帧起始定界符、目标MAC、源MAC、长度/类型字段,以及帧尾的FCS校验。上板测试要确认FPGA发出去的帧符合IEEE 802.3标准,收进来的帧能正确识别帧边界、过滤掉错误帧。

第三层是协议层,也就是IP和UDP是否正确。IP头里的版本、TTL、协议字段、源目的IP、总长度要对,UDP头里的源端口、目的端口、长度、校验和要对。这一层最容易出的问题就是校验和算错,因为校验和计算覆盖的范围包含伪头部、UDP头和payload,流水线稍微打错一拍就全乱。

第四层是应用层,也就是端到端的性能指标。这才是上板测试的最终目的:吞吐率能不能跑到接近线速,丢包率是不是满足应用要求,时延抖动大不大,长时间运行会不会积累错误。

我见过太多人把“上板测试”等同于“跑一下iperf3看看速率”,这是不对的。你需要每一层都有明确的观测手段,出了问题能定位到具体哪一层,否则百G链路调试起来就像在大海里捞针。

1.3 为什么选择开源路线而不是商业IP核

做100G UDP,商业IP核当然成熟,Xilinx和Intel都有全套解决方案,质量高、支持好,但有一个致命的缺点:贵,而且黑盒。

一套100G MAC + UDP offload的商业IP,授权费通常是几十万到上百万人民币级别,而且很多是按项目或按年收费。对于学习验证、预研评估、或者中小团队的项目来说,这个成本很难接受。

开源路线的价值在于,你能看到每一行RTL,能在出问题的时候真正理解硬件在做什么。我自己对开源方案的定位很明确:MAC层用成熟开源项目,UDP协议栈自己写RTL,再加上严格的上板验证。这样既控制了风险,又保留了项目的核心价值——毕竟UDP封装解析和查表逻辑本身并不复杂,真正的know-how在于怎么和高速数据通路结合。

目前开源社区里,Alex Forencich的verilog-ethernet项目是最被广泛使用的,MAC层的10G/25G实现已经非常成熟。100G的MAC,他有一个100G Ethernet MAC的软核实现,但资源占用较大。更实际的路线是用FPGA厂商的100G CMAC硬核——Xilinx UltraScale+系列基本都内置了100G Ethernet MAC硬核,CMAC本身是免费的(只要买了对应芯片),你需要做的只是在外面包一层UDP协议逻辑。这样MAC层免费,UDP层自己写,整个方案完全可控,成本基本为零。

2. 系统架构与开源方案选型

2.1 硬件平台与SerDes通道选择

做100G FPGA设计,平台选择是第一关,决定你是“轻松调试”还是“地狱难度”。

先说芯片。Xilinx UltraScale+家族里的VU9P、VU13P、VU35P等型号集成了100G CMAC硬核,这是最理想的选择。CMAC硬核包含了PMA、PCS、RS-FEC、MAC的完整功能,把你从最痛苦的SerDes和编码逻辑里解放出来。配套的开发板,我推荐VCU118(VU9P),它板载了100G QSFP28光口,是业界做100G验证最常见的板子之一。如果没有VCU118,Alveo U250/U280/U50这种加速卡也可以,它们的100G接口同样好用。

如果你手里只有KCU105这种不带CMAC硬核的板子,也不是不能做100G,但难度会上一个台阶:你需要用GTY收发器自己搭PCS/PMA,自己做64B/66B编码和RS-FEC,资源消耗巨大,调试复杂度也高得多。我通常不建议从这条路走起,除非你的目标就是研究物理层。

再说SerDes配置。100G以太网的实际线速是103.125Gbps,由4个25.78125Gbps通道组成。这里有一个常见的计算误区:很多人以为100G就是4个25G,忽略了编码开销。实际上100G BASE-R的PCS层采用64B/66B编码,每66比特携带64比特有效数据,再加上RS-FEC(544,514)的额外开销,最终线速率才会到103.125G这么个奇怪数字。

CMAC硬核有一个关键好处:它把这些所有底层细节都封装好了,你只需要关心用户侧接口。CMAC输出的用户接口通常是512bit宽的AXI4-Stream,时钟频率大约是322.265625MHz,这样带宽刚好超过100G(512bit x 322MHz = 165Gbps?不对,这里是流水线吞吐而非带宽,实际有效带宽受限制于AXI接口与MAC之间的握手和背压,理论上CMAC接口能跑满100G线速)。换句话说,你的UDP协议逻辑只要能在512bit宽度下每个时钟周期处理一拍,就不会成为瓶颈。

2.2 为什么UDP比TCP更适合FPGA实现

这个问题面试喜欢问,但做工程的人很少会真正去纠结,因为在100G这个量级,答案几乎是唯一的:TCP offload在FPGA里做起来极其痛苦。

TCP是面向连接的可靠传输,每个连接都要维护发送序号、接收序号、确认号、拥塞窗口、接收窗口、重传队列、超时定时器。100Gbps下,如果RTT是10微秒,带宽时延积就是1.25Mbps?不对,100Gbps*10us = 1Mbit(约125KB)。这意味着你需要在FPGA里维护至少125KB的重传缓存,并且每收到一个ACK都要更新状态。更麻烦的是TCP的拥塞控制算法,在硬件里实现慢启动、拥塞避免、快速重传、快速恢复,逻辑复杂度和资源消耗都是UDP的好几十倍。

UDP就完全不一样了。它是无连接的,没有状态机,没有确认,没有重传。硬件只需要做三件事:根据端口查表决定转发方向,计算或校验UDP头校验和,把payload交给应用或者从应用拿过来封装成帧发送。这些操作都是纯粹的流水线逻辑,一拍就能完成查表,再一拍完成校验和,整体延迟可以做到极低。

当然,UDP的代价是丢失数据不负责。所以在实际系统里,应用层协议一般都会加自己的保障机制:比如在payload里加包序号,接收端检查序号连续性就能发现丢包;比如额外加一个CRC32或更高级的校验,防止静默数据损坏。这其实是一种很有意思的架构思路:协议分层,简单留给传输层,可靠由应用层自己保证。用开源方案做100G UDP,你也会自然走上这条路。

2.3 开源方案的整体架构与模块划分

我选的架构是一条非常成熟的通用路线:开源CMAC/MAC做底层,自研UDP协议栈做核心,自定义应用逻辑做验证。

整体数据通路分成两条:

接收路径:QSFP28光口 → CMAC硬核(PMA/PCS/RS-FEC/MAC) → AXI4-Stream 512bit接口 → UDP接收解析模块 → 载荷写入应用FIFO → 应用逻辑处理。

发送路径:应用逻辑产生载荷 → 写入发送FIFO → UDP发送封装模块(生成IP头/UDP头/校验和) → AXI4-Stream 512bit接口 → CMAC硬核 → QSFP28光口。

这个架构的优点在于:所有模块之间的接口统一为标准的AXI4-Stream,给调试和扩展都留了很大空间。比如你想加一个配置寄存器模块,只需要挂在某个AXI-Lite总线上即可;想加CRC校验也容易,在UDP解析之后插一个CRC计算器就行。

关于UDP协议栈部分的代码,网上有一些碎片化的参考实现,比如OpenCores上的udp_ip_stack,或者verilog-ethernet项目里附带的UDP offload模块,但坦白讲,这些都很旧的代码,带宽和位宽都太窄,不能直接用于100G。我的建议是参考它们的结构和状态机设计,但数据通路和控制逻辑要自己根据512bit接口的时序重新写。这也是这个项目最有价值的部分——不是你抄一段代码就能用的,而是你需要真正理解每一拍的时序关系,才能把协议栈和高速接口跑通。

3. 100G UDP协议栈的核心实现细节

3.1 数据通路位宽与时钟计算:512bit @ 322MHz是怎么来的

很多人一开始接触100G系统,最懵的就是这个512bit接口和322MHz时钟是怎么算出来的。

从根上讲,这取决于MAC层的接口设计。对于AXI4-Stream接口,CMAC用户侧的数据位宽可以配置成512bit,也可以配置成1024bit。如果选512bit,那么为了支撑100G的线速,时钟频率至少要达到:100.0609Gbps / 512bit ≈ 195.4MHz?这里要小心,100G以太网MAC的用户接口吞吐通常按(100.06Gbps * 64/66 * 514/544 ≈ 91.7Gbps有效MAC数据率)来计算?不对,让我重新捋一下。

实际CMAC接口的吞吐可以这样估算:PMA线速4x25.78125G = 103.125Gbps,经过64B/66B解码和RS-FEC解码后,流入MAC的用户接口速率大约是100Gbps(线速中去掉编码开销)。这里的100G是MAC接口带宽。CMAC用户接口如果配置成512bit @ 322.265625MHz,那接口带宽是512 * 322.265625 ≈ 165Gbps,远大于100Gbps,所以接口本身不会成为瓶颈。之所以用这么高,一部分原因是为了容纳IFG(帧间隙)、前导码、FCS,以及AXI协议里可能出现的stall周期。

实际上,Xilinx官方推荐的CMAC用户时钟频率就是322.265625MHz(这是由参考时钟156.25MHz倍频得来的),数据位宽512bit。所以你写RTL的时候,心里要有个数:你的设计每个时钟周期必须能处理512bit,也就是最多一个完整的64字节报文片段。任何需要超过一个时钟周期才能完成的处理,都必须在流水线上摊平。

这直接决定了UDP解析模块的写法:不能做那种“等一整帧收完再开始解析”的设计,那样一来FIFO深度会失控,二来延迟会飙高。正确的做法是边收边解析,在第一拍拿到以太网头,第二拍解析IP头,第三拍解析UDP头,通过状态机和流水线的配合,在帧结束之前就完成所有头部解析并给出转发决策。

3.2 UDP接收路径:从字节流到payload的实时解析

接收路径的解析逻辑是整个UDP协议栈的基础。我给你拆一下具体的过程,这段代码值得自己动手写一遍。

进入到UDP接收解析模块的数据是AXI4-Stream,512bit宽。这个模块首先要处理的,是帧边界的识别。AXI4-Stream用tvalid/tready握手,tlast标记帧的最后一个周期,tkeep标记每个字节是否有效。当一帧到达时,tkeep的值会告诉你前导码和帧头在哪个字节偏移上——这是因为来自CMAC的数据流通常是去掉了前导码和SFD的,直接从目标MAC地址开始。

以太网头的固定结构是:目标MAC(6字节)+ 源MAC(6字节)+ EtherType(2字节)。判断是否是IPv4报文,看EtherType是否为0x0800。如果是,紧跟着的就是IPv4头,固定20字节(不含选项)。IPv4头的关键字段:版本/头长度(1字节)、协议(1字节,UDP是17)、源IP(4字节)、目的IP(4字节)。再往后就是UDP头:源端口(2字节)、目的端口(2字节)、长度(2字节)、校验和(2字节)。

在实际RTL实现中,我会把这些头部解析全部改成“偏移量+字段截取”的方式,而不是用复杂的FSM去逐字节匹配。因为512bit的接口上,一帧的头部可能跨2到3个周期,你需要知道每个字段在哪个周期的哪个字节偏移上。这里面最灵活的办法是记录帧头偏移(即第一个tkeep有效时,哪些字节是MAC头),然后按周期递增偏移量来索引后续字段。

还要注意大端序的问题。以太网和IP/UDP协议都是大端序,即最高有效字节在前。你从AXI4-Stream的低字节拿到的是帧的最先到达的字节,如果把这些字节直接当小端序处理,MAC地址、IP地址、端口号全部都会反过来,这是新手最容易犯的错。

UDP校验和的检查,接收端一般是“可选”的,IPv4下可以全零表示未计算,但IPv6下UDP校验和强制有效。不过在实际工程里,我建议接收端无论如何都要做校验和验证,因为你后面做长时间挂机测试时,需要靠这个计数器来捕获罕见的内存或逻辑错误。

3.3 UDP发送路径:头部生成与校验和在线计算

发送路径相对接收路径简单一些,但也有一个关键难点:校验和的计算时机。

UDP校验和覆盖的范围是:伪头部(源IP 4字节 + 目的IP 4字节 + 协议号1字节 + UDP长度2字节,共12字节)、UDP头(8字节)、以及整个payload。这意味着标准的校验和算法需要顺序扫描整个UDP数据报才能得到结果,但发数据的时候,payload是随着时间到来的,不是所有数据都在你面前。

处理方案有两种。第一种是经典的“两遍法”:第一遍先把payload复制到FIFO里并边算校验和,第二遍再从FIFO读出,加上算好的校验和一并发送。这个方案多了一次存储,占用额外的RAM带宽,但在100G下是可行的。第二种是“增量法”:先假设payload的校验和贡献为0,生成一个初始的UDP头(校验和字段填0)发送出去,同时在后台持续计算payload的“一补校验和”;直到帧的最后一个周期payload过完,得到完整的校验和后,再通过一个“修补”机制覆盖到UDP头字段里。

增量法有个工程实现技巧:因为UDP头通常就在帧的开头,而校验和要到帧的最后才知道,很多设计会在帧头处预留一个“修补窗口”——把UDP头的校验和字段先用一个固定寄存器存起来,等收到payload结束信号时,用一个旁路mux在向后传递的流里替换掉那两字节。这种做法在高速设计里很常见,因为它的数据路径延迟只有一个周期(一旦结果算好就插入),不会影响整个帧的连续发送。

IPv4头的校验和覆盖整个IP头,但IP头只有20字节,可以在发送前用组合逻辑一次性算完,然后寄存到IP头字段里,这个相对简单。TTL也需要注意,因为TTL在每一跳都会减一,如果FPGA被多个网关设备转发,TTL设太小会导致丢包。实测时我习惯设成64,与Linux默认值一致,避免踩坑。

3.4 跨时钟域设计:CMAC用户时钟与逻辑时钟的衔接

如果整个设计都跑在同一个322.265625MHz时钟域下,那跨时钟域问题就不存在了,你只需要关心时序收敛。但现实往往是:你的应用逻辑(比如统计、控制)跑在另一个频率,比如200MHz或250MHz,这时就必须做异步FIFO来做位宽转换和时钟域切换。

异步FIFO的深度选择,是上板测试能不能跑到线速的关键。简单算一下:假设应用逻辑时钟200MHz,burst数据率高,而CMAC接口322.265625MHz是稳定消费方,那么当应用逻辑突然灌进来一批数据时,FIFO必须能缓冲这两个频率之间的短期不匹配。如果FIFO深度太小,在流量峰值时会溢出丢包;如果太大,会增加延迟和资源消耗。

以512bit位宽为例,如果要缓冲5微秒的数据,在100Gbps线速下是5us x 12.5GB/s = 62.5KB,而512bit FIFO的一行是64字节,所以需要约1024深度的FIFO。这个量级在UltraScale+的BRAM里很容易满足,但如果缓冲10微秒就需要2048深度,两级拼接等策略也要跟上。你最好先用公式估算,再留出50%的余量。

跨时钟域还有一个坑:异步FIFO的“almost full”信号在不同时钟域之间传递时,天然存在几个周期的延迟。这意味着,当应用逻辑看到almost full再停下来,FIFO可能已经多收了几个周期的数据。所以almost full的阈值要设置得保守一点,我实际测试时一般设在FIFO深度的75%,启动反压,效果比较稳。

3.5 时序收敛的关键路径与优化手段

做100G设计,时序收敛是绕不开的一关。最典型的关键路径是UDP校验和计算器,因为一补校验和的进位链非常长,如果在组合逻辑里一次性算完512bit的校验和,时序几乎一定违例。

我的做法是把校验和计算拆成多级流水线。具体来说:第一级,把512bit分成8个64bit组,分别计算部分和;第二级,把8个部分和相加,同时把前一级的进位纳入计算;第三级,做最终的“进位回卷”操作,即把高16位的进位加回低16位。这样每一级只有约64bit加法器的延迟,时序压力大大降低。

另一个关键路径在以太网帧头的偏移计算和判断逻辑上。帧到达时,要根据tkeep算出当前周期处理的是哪个字节,然后决定当前周期输出的解析动作。这个判断逻辑如果写成大量优先级嵌套的if-else,综合后可能变成很长的组合链。解决办法是把偏移量编码成二进制,用case语句根据偏移量做字段截取,让综合器能优化成更短的逻辑。

时序收敛的另一个重要手段是提升时钟域的整洁性。CMAC出来的时钟如果用作逻辑时钟,最好通过BUFG走全局时钟网络,不要在逻辑内部用ODDR等方式去动态翻转。多时钟域的信号要用同步器或异步FIFO过一下,不要直接拿跨时钟域信号做判断。

4. 上板测试实操全流程

4.1 测试环境搭建:硬件、软件与初始化配置

先说说我的实测环境,给大家一个可复现的基准。

硬件方面:FPGA板我用的是VCU118(VU9P芯片),板载两个QSFP28接口,其中一个接100G光模块和光纤,连接到一台带有100G网卡的服务器。服务器网卡我用过Mellanox ConnectX-5和Intel E810,都支持100G,但用iperf3打流时,Mellanox的驱动和DDP(动态设备个性化)对UDP小包处理更好一些,不容易成为瓶颈。

软件方面:服务器装的是Ubuntu 22.04,需要装好网卡驱动和固件。iperf3要用最新的3.x版本,老版本对100G支持不太好,尤其是多线程UDP模式下,老版本可能会出现无法超过10Gbps的尴尬情况。抓包分析用Wireshark,但要记得在捕获选项里关掉“校验和验证”,因为很多网卡会做checksum offload,抓包看到的校验和可能与线上实际传输的并不一致。

FPGA侧的测试基础设施包括三块:一是ILA(集成逻辑分析仪),接在CMAC用户接口和UDP解析模块的关键信号上,用来抓时序波形;二是一组自定义的性能计数器,用AXI-Lite寄存器暴露给上位机读取,包括收包总数、发包总数、CRC错误数、UDP校验错误数、丢包计数等;三是一个简单的UDP控制通道,上位机可以往特定端口写命令,控制FPGA进入回环模式或开始/停止发包。

初始化流程也很重要:上电后先要完成CMAC的复位和配置,等CMAC的链路状态变为up;然后运行RS-FEC的统计,确认误码率在正常范围;最后才是UDP逻辑的使能。如果CMAC的link都起不来,直接去排查QSFP28光模块的插入方向、光纤有没有插反,以及参考时钟是否稳定。我遇到过几次link up不了的问题,最后都是光模块或光纤的问题,FPGA侧代码反倒没问题。

上板测试的第一步一定是回环。这里的回环分三个层次,从内到外一层层来:第一层是CMAC内部PCS回环,只验证SerDes和PCS;第二层是FPGA内部逻辑回环,即UDP接收模块的输出直接送到UDP发送模块的输入;第三层是外部光口回环,把QSFP28的TX用光纤短接线连到RX。每一层回环都通过了,才说明这一层以下的所有逻辑和物理链路是健康的。

4.2 用iperf3打流:参数选择和常见误区

等回环通过后,重头戏来了:服务器通过100G网卡实际向FPGA打UDP流量。

iperf3的UDP打流命令,给我用得最多的组合是:

iperf3 -u -c 192.168.100.10 -b 100G -l 1400 -t 300 -P 8 --udp-counters-64bit

解释一下参数:-u指定UDP模式,-c指定FPGA侧IP地址,-b 100G指定目标带宽为100Gbps(iperf3会尽量打满带宽),-l 1400指定应用层负载长度为1400字节(这个值意味着以太网帧长约为1414+8+20+14 = 1456字节?实际上这个值会再加上IP和UDP头,所以以太网帧长度约-mtu内的1400+28+14=1442字节,通常在1500 MTU以内,不会分片),-t 300是打流时长300秒,-P 8是8个并行流,--udp-counters-64bit是解决UDP包计数溢出的问题(超过32bit时很关键)。

这里有个很容易踩的坑:很多人直接跑iperf3 -u -c xxx -b 100G,结果发现速率最多只能到10Gbps左右。这不是FPGA的问题,而是iperf3默认单线程,而且UDP发送要经过协议栈,CPU成为瓶颈。解决方法是加-P多开几个并行流,把CPU多核用起来。8个并行流是我测试下来比较稳定的配置,再多了反而会因为线程调度开销导致速率下降。

另一个坑和MTU相关。服务器网卡默认MTU通常是1500,如果你想把吞吐跑到接近线速,建议把MTU调到9000(巨型帧)。因为以太网帧长越大,前导码、IFG、帧头的开销占比就越小,有效载荷率从约98.8%(1500字节帧)提升到约99.8%(9000字节帧)?更重要的是,大帧减少了每秒处理的帧数,减轻了CPU和硬件解析的负担。设置MTU的命令是:

sudo ip link set dev enp1s0f0 mtu 9000

4.3 性能计数器设计和吞吐率计算

下面是我在FPGA里加的几组关键计数器,这些计数器是上板测试能够“看到”性能数据的眼睛。

收包总数(rx_pkt_cnt):每收到一个完整UDP帧(tlast有效且无错误标记)加1。这个数要和服务器发送的包数做减法来算丢包率。

CRC错误计数(rx_crc_err_cnt):CMAC输出的tuser信号里通常会带一个错误标记,表示该帧FCS校验失败。这个计数器独立于UDP解析逻辑,用来快速定位是不是MAC层有问题。

UDP校验和错误计数(rx_udp_cksum_err_cnt):UDP解析模块计算校验和发现错误时加1。如果这个数不为零而CRC计数为零,说明数据在MAC层之后被你的逻辑搞坏了。

FIFO溢出丢包计数(rx_fifo_overflow_cnt):应用侧FIFO发出almost full信号后,仍然被写入到满时,丢帧计数器加1。这个是判断设计瓶颈的关键指标。

以太网净吞吐率怎么算?一个直接的公式是:应用层有效速率 = (每秒收到的payload总字节数)/ 时间。iperf3的Server端输出会直接告诉你收到多少数据,除以持续时间就是有效吞吐率。100Gbps线速对应的iperf3停止时的Receive速率大约在94Gbps到99Gbps之间,取决于帧长和MTU设置。如果MTU是1500,那iperf3测到95Gbps左右就已经接近上限了,不是FPGA没跑满,而是以太网协议本身的开销决定了不可能到100G。

我实测过一个典型结果:MTU 9000,8流并发,FPGA回环模式,iperf3测得的有效吞吐率大约是98.5Gbps,丢包率为0,这个结果说明整个链路已经没有实质瓶颈了。

4.4 Wireshark抓包和payload完整性校验

打流测试跑起来以后,很多人只看“有没有丢包”,但“收到的数据对不对”其实更重要。UDP传输过程中如果内存位翻转、FIFO读写错位或者复位不彻底,payload里可能会出现Burst error,这种错误靠丢包率是发现不了的。

验证payload完整性的办法有两个方向。一是在FPGA上板时,自己发固定pattern的UDP包,比如payload是递增计数、PRBS序列或固定的0x5A5A,上位机收到后做pattern比对,一有误码立刻能发现。二是反过来,上位机发固定pattern,FPGA收下来之后做CRC比对,并统计错误帧数。

如果你用iperf3测试,它自带的payload不是固定pattern,而是随机数据,不适合做完整性的逐字节比对。所以我推荐在FPGA里额外加一个prbs_check模块,放在UDP解析之后,对payload做PRBS校验。上位机打流用iperf3也好,用自写脚本也好,只要payload里有对应的PRBS序列,这个模块就能实时报出错位置。

Wireshark在这里的作用主要是看协议层的正确性。抓包时你重点看三件事:一是源/目的IP字段是否正确;二是UDP端口是否按应用配置的规则在走;三是UDP校验和是否显示为“incorrect”。如果Wireshark大量显示UDP checksum incorrect,别急着怀疑FPGA,先用ethtool -K 关闭网卡的checksum offload再看一遍,很多时候是网卡驱动替你把校验和计算了,而抓包软件拿到的是offload前的数据。

4.5 长稳测试:跑12小时看什么指标

短时间打流通过不算完,以太网系统最怕的是偶然性错误。我之前就遇到过一个案例:回环测试跑1小时零丢包,结果放到数据中心环境里,每过几个小时就会出现一个CRC错误,排查了很久最后发现是参考时钟的抖动在某些温度点超标。

所以长稳测试的核心逻辑是:跑得足够久,让偶发问题暴露出来。

我推荐的长时间测试配置:打流12小时,MTU 9000,8流并发,速率80Gbps(留20%裕量)。每5分钟记录一次FPGA侧计数器的值,包括收包总数、CRC错误数、UDP校验错误数、FIFO溢出数。同时记录服务器侧iperf3的最终结果。12小时后比对两者差异,如果丢包率在10的负10次方以下,CRC错误和校验错误全部为0,通常可以认定这套设计是稳定的。

另外还要观察一个指标:运行过程中有没有出现链路中断。如果CMAC或光模块的link偶尔掉一下又自动恢复,那说明物理层还有隐患。这种问题短时间打流很难抓出来,只能靠长稳测试。我一般会在FPGA里加一个link_loss_count计数器,记录CMAC链路down的次数,长稳结束后如果这个数不是0,就得认真查光模块、光纤和参考时钟了。

4.6 多速率和多包长下的压测摸底

等你把单点配置调优之后,我强烈建议做一轮多条件压测,把设计的性能边界摸清楚。

常见的测试矩阵是:带宽从10G慢慢往上加,50G、70G、90G、100G;包长从64字节、128字节、512字节、1400字节、9000字节各测一轮;并行流数从1到16。你会发现一个有意思的现象:64字节小包时,即使带宽只有20Gbps,丢包率也可能很高,因为每秒的包数量太大,FPGA的查表和解析逻辑每拍最多处理一个帧起点,处理不过来。这其实暴露的是“每秒包数(pps)”能力限制,不是带宽限制。

例如,64字节最小以太网帧加上IFG和前导码,线上总长是84字节,100G线速对应的最大包速率是100Gbps / (84 * 8) ≈ 148.8Mpps。如果FPGA解析逻辑每拍只能处理一个包,那么512bit接口能支持的包速率上限是 322MHz / (每帧需要的周期数)。如果每帧至少需要2拍(因为64字节帧在512bit接口上只占1拍),那么大约能支持161Mpps,勉强够100G线速小包的能力。但如果你的解析逻辑每帧需要3拍,那就只能到107Mpps,100G小包线速必然丢包。

这类压测结果要清楚记录,它能告诉你你这套方案适合跑什么样的业务。比如如果小包线速跑不满,你就知道这套设计适合大数据包传输(数据采集、文件镜像),不适合小包高并发(比如金融行情、信令面)。

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

5.1 链路起不来:link up不了怎么办

这是上板测试遇到的第一个高频问题。现象是服务器网卡侧看不到链路,或者FPGA侧CMAC状态寄存器显示link down。

排查顺序有讲究,按从外到里的顺序来。

先看物理连接:QSFP28光模块插到位没有,光纤是不是插反了(TX对RX),光模块的型号和板子支不支持。我踩过最冤枉的一次坑是光模块插在板上,但没插到底,导致link灯一直不亮。

再看光信号质量:正常工作时,光模块DDM信息里的TX/RX power应该在合理范围,比如-3dBm附近,如果RX power显示-20dBm以下,说明光纤或光模块有问题,信号衰减太大。

其次看参考时钟:用示波器或频率计看下CMAC参考时钟(通常是156.25MHz)的稳定度。时钟漂移或者幅值不足会导致SerDes无法锁定,进而link up不了。

最后看RS-FEC配置:如果线缆或光模块质量较差,关闭RS-FEC可能导致链路频繁误码甚至无法建立。Xilinx CMAC在100G模式默认开启RS-FEC,不要轻易关。

这一层问题基本都能通过查看CMAC的调试端口(如tx_axis_aresetn、rx_axis_aresetn、status寄存器)来定位。

5.2 疯狂CRC Error:字节序和位序的坑

如果link已经起来了,但收到的帧大量CRC错误,那大概率是你自己的逻辑没有正确对齐MAC层的字节序。

我拿自己的教训举个例子。第一次搭这个项目时,我把AXI4-Stream的512bit数据直接当成小端序处理,结果目标MAC地址解析出来是反的,CRC自然全错。后来翻了CMAC的手册,才知道它输出的字节顺序是“大端序排列”,即数据流的第一个字节出现在512bit数据总线的最低8位上。你从低位到高位拼起来的数据,才是以太网帧的正确顺序。

如果确认字节序没问题,那就要查FCS校验的覆盖范围。CMAC硬核自带FCS校验,但你如果自己实现MAC,就得确保CRC32的计算范围覆盖从目标MAC地址到payload的末字节,但不包括前导码和FCS本身。计算完还要做一次取反,并按照以太网规范把小端序输出。

还有一种情况:CRC错误只发生在偶发的帧上,比例在万分之一到百万分之一之间。这时就要考虑是否是物理层误码引起的,比如光模块老化、链路的RS-FEC纠错能力已经接近极限。可以看看CMAC的RS-FEC误码统计,如果纠错次数非常高,那问题在物理层,不在你的逻辑。

5.3 一打流就丢包:吞吐瓶颈在哪里

“发小包不丢,大包也不丢,但跑满带宽就丢”这种情况是最常见的性能瓶颈问题。

首先确认丢包的点。在FPGA里,你要把丢包计数器分开来设计,比如刚才提到的FIFO溢出计数、解析模块的“无法处理”计数、MAC层的暂停帧统计等。如果只有FIFO溢出计数在涨,说明FIFO缓冲深度不够或反压不及时。

如果FIFO深度已经够大,还是要丢,那就要看反压路径的延迟。注意AXI4-Stream的tready信号通过异步FIFO到应用逻辑再回到发送侧,这个环路如果太长,发送方可能已经在路上又发了几个周期,导致FIFO依然溢出。解决办法是把几乎满(almost_full)信号的阈值设置在75%,给反压路径留出足够余量。

还有一种容易忽略的丢包原因是时钟精度,比如CMAC用户时钟如果来源有偏差,实际频率比理论值低1%,那无论你FIFO多深,最终都会因为“生产速率>消费速率”而丢包。这种问题靠提升FIFO深度解决不了,必须检查时钟源。

5.4 UDP校验和看起来不对

这个问题,排查的时候要分清是“真的不对”还是“工具误报”。

真的不对的情况,就是我前面提到的伪头部顺序搞错了。UDP校验和的伪头部排序是源IP前4字节,目的IP后4字节,协议号1字节,之后补1字节零,然后是UDP长度2字节。很多人在实现时把协议号放错位置,或者漏掉了补的那个零字节,都会导致校验和恒错。

工具误报的情况,主要是Wireshark抓包环境下网卡的checksum offload导致的。上面已经说过,用ethtool -K eth0 tx off rx off关闭offload再抓包,就能排除干扰。

另外补充一个硬件细节:UDP校验和的加法规则是一补码加法,也就是说,所有16位字的和如果产生了进位,要把进位回卷加到最低位,最后再取反。如果直接用普通的二进制加法器而不做进位回卷,算出来的校验和在高负载时就会偶发错误。

5.5 长时间运行后性能劣化

有些设计刚上电时性能很好,跑几个小时后开始出现间歇性丢包或CRC错误,这种问题最难查,因为它不是固定的。

最常见的两个元凶,一个是内存或FIFO的软错误(单粒子翻转),另一个是热漂移导致的时序变化。

对于软错误,尤其是在数据中心环境,高能粒子或宇宙射线打到BRAM上可能导致数据翻转。如果错误帧的CRC错误是零星分布、不规律出现的,可以考虑对关键表项(比如MAC地址表、统计计数器的备份表)加上三模冗余或ECC。Xilinx的BRAM可以配置成ECC模式,能够纠正单比特错误,检测双比特错误,这个功能在高可靠场景下强烈推荐开启。

对于热漂移,如果板子散热不良,长时间运行后芯片温度升高,时序余量会逐渐变小,最终导致偶发时序违例。排查办法是记录长稳测试期间的芯片温度,如果温度在上升而错误开始出现,就需要加强散热或者降低运行频率。

写在最后:一些值得记住的实测体会

这套方案从我最早在实验室搭起来,到后来放到实际项目里跑业务流量,中间迭代了好几轮,最有感触的两点:一是上板测试的工作量真的不比写RTL少,甚至更多;二是90%的问题都出在最基础的连接、字节序、反压和时钟上,真正协议层面的疑难杂症反而很少。

最后分享一个我一直在用的小习惯:在FPGA里把所有的性能计数器都通过AXI-Lite寄存器暴露出来,上位机写一个小脚本,每秒钟读取一次并打印到屏幕。这样做的好处是,当系统表现异常时,你能立刻看到是接收路径在丢包还是发送路径在丢包,是链路质量下降还是FIFO溢出,而不是对着iperf3的输出瞎猜。这个“可观测性”的设计思路,比任何高级调试技巧都管用。

如果你正在做类似的100G项目,希望这篇内容能帮你节省一些调试时间。也欢迎你踩过更隐蔽的坑以后回来交流,毕竟100G以太网这东西,永远能在你意想不到的地方给你上一课。

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

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

立即咨询