做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和编码逻辑里解放出来。配套的开发板我推荐VCU118VU9P它板载了100G QSFP28光口是业界做100G验证最常见的板子之一。如果没有VCU118Alveo 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这样带宽刚好超过100G512bit 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-Stream512bit宽。这个模块首先要处理的是帧边界的识别。AXI4-Stream用tvalid/tready握手tlast标记帧的最后一个周期tkeep标记每个字节是否有效。当一帧到达时tkeep的值会告诉你前导码和帧头在哪个字节偏移上——这是因为来自CMAC的数据流通常是去掉了前导码和SFD的直接从目标MAC地址开始。以太网头的固定结构是目标MAC6字节 源MAC6字节 EtherType2字节。判断是否是IPv4报文看EtherType是否为0x0800。如果是紧跟着的就是IPv4头固定20字节不含选项。IPv4头的关键字段版本/头长度1字节、协议1字节UDP是17、源IP4字节、目的IP4字节。再往后就是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的深度选择是上板测试能不能跑到线速的关键。简单算一下假设应用逻辑时钟200MHzburst数据率高而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板我用的是VCU118VU9P芯片板载两个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指定目标带宽为100Gbpsiperf3会尽量打满带宽-l 1400指定应用层负载长度为1400字节这个值意味着以太网帧长约为141482014 1456字节实际上这个值会再加上IP和UDP头所以以太网帧长度约-mtu内的140028141442字节通常在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 90004.3 性能计数器设计和吞吐率计算下面是我在FPGA里加的几组关键计数器这些计数器是上板测试能够“看到”性能数据的眼睛。收包总数rx_pkt_cnt每收到一个完整UDP帧tlast有效且无错误标记加1。这个数要和服务器发送的包数做减法来算丢包率。CRC错误计数rx_crc_err_cntCMAC输出的tuser信号里通常会带一个错误标记表示该帧FCS校验失败。这个计数器独立于UDP解析逻辑用来快速定位是不是MAC层有问题。UDP校验和错误计数rx_udp_cksum_err_cntUDP解析模块计算校验和发现错误时加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 90008流并发FPGA回环模式iperf3测得的有效吞吐率大约是98.5Gbps丢包率为0这个结果说明整个链路已经没有实质瓶颈了。4.4 Wireshark抓包和payload完整性校验打流测试跑起来以后很多人只看“有没有丢包”但“收到的数据对不对”其实更重要。UDP传输过程中如果内存位翻转、FIFO读写错位或者复位不彻底payload里可能会出现Burst error这种错误靠丢包率是发现不了的。验证payload完整性的办法有两个方向。一是在FPGA上板时自己发固定pattern的UDP包比如payload是递增计数、PRBS序列或固定的0x5A5A上位机收到后做pattern比对一有误码立刻能发现。二是反过来上位机发固定patternFPGA收下来之后做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 90008流并发速率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拍那就只能到107Mpps100G小包线速必然丢包。这类压测结果要清楚记录它能告诉你你这套方案适合跑什么样的业务。比如如果小包线速跑不满你就知道这套设计适合大数据包传输数据采集、文件镜像不适合小包高并发比如金融行情、信令面。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以太网这东西永远能在你意想不到的地方给你上一课。
