ARTICLE DETAIL

资讯详情

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

开源100G UDP协议栈移植到UltraScale+板卡全流程实战

开源100G UDP协议栈移植到UltraScale+板卡全流程实战 开源代码能跑通仿真和真正上板跑通100G是两码事尤其当目标板卡不是项目原作者手里的那块板子时移植这一层会消耗掉一半的精力。这篇文章把我移植开源100G UDP协议栈到Xilinx UltraScale板卡的完整过程记录下来包括中间踩过的所有坑、改过的每一类代码以及实测的吞吐数据给准备在FPGA上做100G网络处理的朋友一个参考。我这次选用的开源工程是GitHub上活跃度很高的verilog-ethernet作者是Alex ForencichMIT协议代码结构非常清晰从10G到100G都有现成的MAC和UDP协议栈实现。实际动手之前我把它当成一个黑盒来用移植完之后才发现要想真正上板跑满带宽里头每个模块的握手方式、背压策略、时钟关系都必须吃透。这篇就按我从方案选型到上板测试的完整顺序来写侧重于移植过程中的决策逻辑和实测环节不堆概念尽量把每一步为什么这么做讲清楚。1. 项目背景与方案选型1.1 100G UDP到底在什么场景下非用不可很多朋友一开始都会问为什么偏偏是100G还要用UDP而不是TCP。我这边的情况是实验室要搭建一套高速数据采集系统前端ADC采样率非常高数据经过预处理之后需要实时送到服务器集群做分析。数据特征是持续大流量、突发性强、对时延敏感但允许一定范围内的丢包后重传或者丢弃。这种场景下TCP的拥塞控制和重传机制反而成为负担UDP配合应用层的丢包补偿策略更合适。另一个更常见的场景是数据中心里的分布式存储和AI训练集群节点之间需要大带宽的数据搬运。100G以太网经过这几年的普及光模块和交换机的成本已经降了不少FPGA做100G的数据面加速越来越常见。相比ASICFPGA的优势在于可重配、开发周期短尤其适合协议还没完全固化、需要频繁迭代的原型验证阶段。还有个容易被忽略的点100G UDP对FPGA设计来说是一个完全不同的量级。10G时代数据位宽才64bit时钟156.25MHz一个包的处理可以在好几个周期里慢慢来。到了100G数据路径通常做到512bit用户时钟要跑到322MHz左右一个时钟周期要处理64字节这意味着核心逻辑必须在几个周期内完成一整包数据的解析和转发设计难度是几何级上升的。1.2 方案选型自研、商业IP还是开源移植100G UDP协议栈的获取方式大致有三条路完全自研、购买商业IP、移植开源代码。三条路我都评估过这里把对比列出来给同样在纠结的朋友一个参考。方案人力成本资金成本灵活度风险点适用场景完全自研3-6人月低最高以太网协议细节多100G时序收敛困难需要深度定制、长期演进的产品商业IP少高按项目授权低通常加密交付供应商绑定定制困难量产产品、追求稳定性和技术支持开源移植2-4周免费中高源码可改文档少需要自己消化代码科研验证、快速原型、学习研究我最终选了开源移植原因很简单项目时间紧预算有限而且我需要能够修改协议栈内部逻辑做定制化过滤商业IP的黑盒模式基本堵死了这条路。自研的话光是以太网CRC、校验和、ARP超时处理这些细节就够折腾两个月还不算100G时序收敛的工程量。选开源方案还有一个好处verilog-ethernet这个项目本身对Xilinx的CMAC硬核支持已经做得很完整相当于把最难的那部分高速串行接口适配工作已经完成了大半。我需要的更多是把它接到自己板卡的光模块和用户逻辑上。1.3 开源工程的核心模块与分工verilog-ethernet工程的结构简单说就是把以太网协议栈按层拆成独立模块每一层都可以单独使用也可以组合成完整协议栈。从数据流方向看最底层是MAC层对应工程里的eth_mac系列模块负责以太网帧的收发、CRC校验、帧间隙插入等。在Xilinx平台上有对应的硬核封装比如eth_xilinx_100g_cmac就是包了一层CMAC硬核的适配逻辑。往上一层是IP层包含IP发送、接收、ARP缓存、ICMP回复等模块。最上面才是UDP层有udp_stack、udp_rx_engine、udp_tx_engine这些核心文件。我当时理解这个工程花了不少时间建议第一次接触的朋友不要上来就看代码细节先按数据流把每个模块的输入输出接口捋一遍。比如udp_stack对外暴露的接口接收方向是用户逻辑把数据包交给协议栈发送方向是协议栈把解析后的UDP负载交给用户逻辑链路层的MAC地址、IP地址、端口号过滤这些配置则是通过一组register接口来设置。理清这个结构之后再看代码效率会高很多。2. 100G UDP协议栈核心原理拆解2.1 100G数据通路512bit并行到底意味着什么做过10G以太网的朋友应该对64bit数据位宽、156.25MHz时钟很熟悉。100G时代如果继续用64bit路径时钟要跑到1.5625GHz这在FPGA里完全不可行所以只能加宽数据总线。Xilinx的CMAC硬核在100G模式下常见配置就是512bit数据位宽配上322.265625MHz左右的用户时钟。这个变化带来的第一个问题就是一个时钟周期内数据总线最多能装下64字节正好等于一个最小以太网帧的帧头加负载加CRC的长度。也就是说一个小包可能在同一个周期内就完整到达并结束你的处理逻辑必须在一个周期内完成对这个包的识别和转发决策。这就逼着你把所有解析逻辑做得尽量简单、流水化。举个例子10G时代判断一个UDP包的目的端口可以先缓存帧头等几个周期慢慢比较。100G时代如果这么做下一拍数据已经跟上来缓冲区很容易被冲爆。我后面实现的时候端口过滤逻辑全部做成了组合逻辑直接比较tdata总线上的对应字节位置几个比较器并行工作一个周期出结果这样才能追上数据速率。第二个问题是包与包之间的间隙和跨包处理。由于数据总线很宽一个AXI事务里往往包含多个以太网帧tlast信号标记每个帧的结束。开源代码里有专门的跨包处理逻辑需要正确识别tlast之后下一个tvalid是不是新的一帧这里稍有不慎就会把帧边界搞错出现该丢的没丢、不该丢的被吞的问题。2.2 CMAC硬核与开源wrapper的衔接逻辑Xilinx UltraScale系列的CMAC是一个硬核负责和外部光模块之间的物理层对接包括串并转换、64B/66B编解码、FEC、时钟恢复、链路同步等。它对外提供标准的AXI4-Stream接口数据位宽、KEEP信号、USER信号都定义好了。用户逻辑其实是站在CMAC的MAC层之上应该只看到完整的数据帧。但直接用CMAC也不是不行问题是CMAC出来的AXI-Stream接口上tuser信号会在某些错误场景下被拉高比如CRC错误、帧长度错误、控制字符错误等。如果不去管这个信号这些坏帧会一路送进UDP协议栈造成解析错误。verilog-ethernet的eth_xilinx_100g_cmac模块做的就是这件事它把CMAC的tuser错误标志转成帧级别的错误标记然后由MAC层的接收端把这些坏帧直接丢弃。这块还有一个关键点是CMAC的复位时序。CMAC复位分很多种包括GTY收发器的复位、PCS复位、MAC复位、AXI-Stream接口复位它们的先后顺序有严格要求。顺序不对会导致链路起不来或者link status状态一直不稳定。我在第一次上板时就因为漏看了文档里的复位时序要求导致GTY一直报aligned信号拉不起来后来按照Xilinx官方手册里推荐的复位流程重写了一遍问题才解决。2.3 UDP协议栈的模块化设计思路udp_stack这个顶层模块内部其实包含了UDP接收引擎、UDP发送引擎、IP接收发送、ARP缓存、ICMP回显响应等多个子模块。这些子模块通过内部信号互相连接对外只暴露一个相对简洁的接口。具体到UDP接收路径数据流是这样的MAC层过滤掉CRC错误的帧之后把以太网帧交给IP层IP层检查IP头部版本、校验和、目的IP地址是否匹配匹配的话再交给UDP层。UDP层解析UDP头部校验端口号然后通过用户接口把负载数据送出去。同时如果接收到的UDP包目的端口是已知的还可以配置成可回环loopback模式用于调试。发送方向上用户逻辑要发一个UDP包只需要在udp_stack的发送接口上提供目标IP、目标端口、源端口、负载数据协议栈会自动生成完整的UDP头部、IP头部和以太网头部。这里注意一个细节UDP校验和的计算在硬件上是个麻烦事需要把伪头部、UDP头部和负载数据全部累加。开源工程提供了增量更新incremental update的机制可以在数据逐拍流入时并行累加而不是等整包数据收齐再算这样延迟低很多。这里值得展开讲讲校验和的实现细节。传统做法是包收完了再从缓冲区读一次做16位反码求和这样要多花一整包的存储和读取时间。增量更新的思路是把校验和计算分散到数据流经过的每一个周期每个周期对当拍的有效字节做部分累加整包结束后再把累加值修正成标准的16位反码校验和。这样做的好处是校验和计算完全流水化不额外增加存储代价是逻辑复杂度稍微高一点需要对首部字段的分布位置做精确控制。3. 移植上板的完整实操流程3.1 工程环境与板卡准备先说下我这次的环境Vivado 2022.1板卡是一块UltraScale VU9P的开发板板上带一个QSFP28光模块接口光模块用的是100G SR4需要配套MPO光纤和一台带100G网卡的服务器作为对端。服务器网卡我用的Mellanox ConnectX-5这个卡在Linux下的驱动支持很成熟iperf3测试稳定。动手之前有几个硬件信息一定要先从板卡原理图确认清楚一是GTY的参考时钟连接到哪个BANK、用的什么频率一般100G模式要求161.1328125MHz的参考时钟二是光模块的管理接口I2C是否连接到了FPGA还是板载PCIe控制器三是QSFPDD或者QSFP28的复位脚、中断脚默认电平是什么。这些信息如果看错了后面链路起不来的时候排查起来非常痛苦。我这次比较幸运板卡的GTY参考时钟默认就接到了正确的BANK而且频率是标准的161.13MHz不用动硬件。但光模块的I2C总线被板载的PCIe桥接芯片占用了这意味着我没法通过FPGA直接读光模块的寄存器来查询光模块状态只能靠CMAC内部的信号判断链路状态。后来实际调试发现影响不大CMAC的aligned信号和光模块的LOS信号基本能反映链路健康度。3.2 创建工程与添加开源代码工程创建这部分其实没什么特别的常规操作。需要注意的一点是verilog-ethernet工程的文件结构里不同速率的MAC文件非常多不要一股脑全部添加进来否则综合的时候会报一些莫名其妙的端口不匹配冲突。我建议只添加实际用到的文件我的做法是按照数据路径的最低依赖一个一个加。最终我加进来的核心文件大概是这些lib/eth/下的eth_mac_100g相关文件、lib/ip/下的IP收发和ARP缓存、lib/udp/下的udp_stack等、lib/axis/下的axis_fifo和axis_adapter做缓冲和位宽转换以及rtl/xilinx/下的eth_xilinx_100g_cmac模块。添加顺序按依赖关系来先加底层的axis库再加eth库然后ip和udp最后才是xilinx适配层。Vivado的compile order一般能自动判断但手工维护好顺序综合报错时排查起来会省很多事。CMAC IP核的例化是这部分的重点。我用的Vivado IP Catalog里直接搜CMAC新建IP后需要配置线速率、数据位宽、是否使能RS-FEC等参数。这里我踩了一个坑一开始为了省事没有使能RS-FEC结果100G SR4光模块在短距离直连的情况下也能通但后来换成长距离的LR4模块时误码率明显升高链路经常丢包。后来重新配置成使能RS-FEC误码问题才解决。如果你的应用场景是数据中心内部短距离不开FEC问题不大一旦涉及较长光纤或者光模块质量不稳定FEC几乎成了必需品。3.3 时钟、复位与关键接口连接CMAC例化完成后工程里会多出几个关键时钟域。GTY的参考时钟来自板卡上的固定晶振CMAC内部恢复出来的用户时钟会从ip_core接口输出这个时钟作为整个UDP协议栈和用户逻辑的工作时钟。我自己的用户逻辑全部跑在这个时钟域里没有再跨时钟到PCIe或DDR等其他时钟域这样做的好处是设计里只有一条高速数据通路时序约束相对集中。复位的连接也要小心。CMAC的复位分GTY复位和AXI-Stream接口复位两者需要按顺序释放。我在工程里做了一个简单的复位状态机上电后先等待GTY的tx/rx reset done信号拉高再延时一段时间释放AXI-Stream接口的复位。这个逻辑如果写复杂了反而容易出错我的经验是越简单越好关键是满足时序顺序。最后是AXI-Stream接口的连接。CMAC的m_axis_rx_tdata 512bit、m_axis_rx_tkeep 64bit、m_axis_rx_tvalid、m_axis_rx_tlast这些信号直接连到eth_xilinx_100g_cmac的输入端口这个模块会做一次信号转换后输出一套干净的、不带CMAC底层错误标记的帧信号给UDP协议栈。发送方向类似udp_stack产生的帧经过MAC层的发送FIFO送到CMAC的s_axis_tx接口。3.4 逻辑仿真与初步验证上板之前一定要做仿真这一步能省掉上板后至少一半的调试时间。我用的是Vivado自带的仿真器测试平台也很简单用testbench给udp_stack的发送接口注入一个UDP报文然后在接收端回环验证数据是否正确再模拟外部主机发来一个ARP请求看协议栈是否正确回复ARP应答。仿真时特别注意几个关键点一是复位时序测试平台里要把复位释放顺序模拟出来二是AXI-Stream的tready信号反压需要测试在用户逻辑不ready的情况下协议栈是否正确暂停发送三是UDP校验和计算是否正确可以在testbench里用Python预先算好标准校验和然后比对。我认为仿真不能只测正常路径异常路径更要测。比如故意构造一个CRC错误的帧看协议栈是否丢弃构造一个目的IP不匹配的包看是否被过滤构造一个UDP长度字段和实际负载长度不符的包看接收引擎是否正常处理边界。这些异常场景如果在仿真阶段就暴露上板后就不用一边抓波形一边猜问题。3.5 上板测试步骤与实测数据当仿真用例全部通过后就可以开始上板了。我的上板测试流程分了五个步骤每步都通过了再进入下一步避免一次堆叠太多变量出问题都不知道怪谁。第一步是烧录和基础状态检查。通过ILA抓CMAC的状态寄存器确认GTY的tx/rx aligned信号拉高、CMAC的link status信号正常。这一步大概花了几十分钟主要原因是第一次上板居然发现aligned信号一直不拉高后来排查发现是GTY参考时钟的约束没写对综合工具把参考时钟约束成了错误的频率。第二步是L2连通性测试。在服务器上把网卡配成和FPGA同一网段的IP然后用arping命令向FPGA的MAC地址发ARP请求。如果FPGA正确响应ARP说明以太网链路基本打通。我当时用arping测试FPGA的ARP响应很稳定这给了我继续往下测的信心。第三步是L3连通性测试也就是ping测试。FPGA端我用ILA捕获ICMP请求报文同时看协议栈是否发出ICMP回显响应。ping的通说明IP层的收发、校验和处理都正常。这里有个小细节如果FPGA端用硬接线方式配置IP地址端口号固定ping测试只验证ICMP回显功能UDP端口过滤还不涉及。第四步是UDP回环测试。我在udp_stack的用户接口上搭了一个简单的回环逻辑把接收到的UDP负载原封不动从发送接口发回去。服务器端用自定义UDP小工具发一段固定数据再接收回环的数据并比对。这一步主要验证UDP协议栈内部收发通路以及用户接口对接是否正确。第五步是真正的性能测试。我用iperf3以UDP模式从服务器向FPGA发送数据流再把FPGA回环后的数据用iperf3服务器端统计接收速率。最终实测结果让我挺满意UDP负载为1472字节时服务器侧接收速率稳定在98.4Gbps左右丢包率在万分之一以下基本达到了100G线速。后来我去掉了回环逻辑改成FPGA直接向服务器发送实测也能跑到98Gbps左右。4. 上板测试中的典型问题与排查实录4.1 链路起不来GTY参考时钟与CMAC复位第一次上板时遇到的第一个问题就是GTY的aligned信号一直拉不高链路根本建立不起来。当时ILA抓看到的信号是tx_reset_done和rx_reset_done都正常但rx_aligned就是一直在跳。排查过程从检查参考时钟开始。我用ILA抓了GTY的txoutclk和rxoutclk发现频率都是正常的说明GTY内部的PLL已经锁定。然后我怀疑是CMAC复位顺序问题于是仔细对照Xilinx PG203里的复位时序图发现文档要求GTY的复位释放之后至少要等一段时间再释放CMAC PCS复位最后才是AXI-Stream接口复位。我原先的复位逻辑里这几个复位几乎是同时释放的违反了时序要求。重新写了一个简单的复位状态机按照文档顺序依次释放复位再上板测试aligned信号稳定拉高链路成功建立。这个问题让我花了大半天时间所以这里特别提醒大家CMAC的复位时序一定要严格参照官方文档不要凭感觉来。4.2 能通但丢包率异常FIFO背压与数据跨包处理链路通了之后我开始跑小流量UDP测试发现虽然能通但一旦流量上来服务器的接收端丢包率就明显上升大概到20%左右。这个丢包率远高于预期开始以为是光纤或者光模块问题但换过光纤之后并没有改善于是判断问题出在FPGA内部数据通路上。我用ILA同时抓了udp_rx_engine的数据接口和用户逻辑的接收接口发现当用户逻辑把tready拉低时udp_rx_engine前面的FIFO很快就填满了后面的包自然就被丢弃了。问题根源在于我的用户逻辑处理速度跟不上线速。我原先的用户接口只接了一个简单的双端口RAM写入逻辑每包数据之间还有处理延迟无法持续消费数据流。解决办法是在用户逻辑前加了一个大深度的axis_fifo做缓冲同时优化了用户逻辑的消费效率把数据直接以512bit粒度写入DDR而不是按字节处理。另外把axis_fifo的almost_full阈值调低给上游留出提前反压的余量。调整之后丢包率从20%降到了万分之一以下。4.3 时序收敛问题复位路径与数据路径的瓶颈性能测试通过之后还有一件非常重要的事就是确保工程在布局布线之后时序收敛。第一次综合布线后时序报告显示有几个关键路径违例主要分布在CMAC送出来的复位信号路径和UDP接收引擎内部的跨包判断逻辑上。复位信号的路径违例比较典型因为复位信号扇出非常大要驱动几百个寄存器布线延迟叠加起来很容易超过时序要求。我采取的优化手段是使用同步复位并且在每个子模块内部加了一级复位同步器尽量把复位信号的负载分散到局部。另外把综合的directive改成PerformanceExplore让工具更激进地优化性能。数据路径上的违例主要是udp_rx_engine处理跨包边界时组合逻辑太长一个周期内既要判断tlast、又要比较端口、还要跳转FIFO写指针。我把判断逻辑拆成了两级流水第一级判断tlast和端口匹配第二级根据结果更新状态机。代价是产生一个周期的延迟但换来的是时序收敛这个交换非常值得。4.4 几个容易被忽略的细节第一是和KEEP信号相关的字节对齐问题。UDP负载的长度不一定是4字节对齐当负载长度不是4的倍数时最后一拍的tkeep信号就不全为1。如果用户逻辑在这个边界上没有正确处理会导致数据写入DDR时地址错位。第二是tuser信号的过滤。CMAC出来的AXI-Stream接口上tuser会在帧错误时被拉高。如果直接在用户逻辑里忽略这个信号坏帧就会被当成正常帧处理。我在eth_xilinx_100g_cmac适配模块里已经处理了但如果你是自己直接接CMAC一定要把这层过滤加回去。第三是UDP端口过滤的配置。udp_stack的接收端口过滤默认情况下只接收配置好的端口。如果测试时随便指定一个端口发数据协议栈会直接丢弃。我当时测试时用了自定义端口号一度以为协议栈收不到数据后来检查才发现是端口号没对上。4.5 问题排查速查表为了方便大家移植时快速定位问题我把这次遇到的主要情况整理成一个速查表给同样踩坑的朋友一个参考。现象可能原因排查方法解决办法GTY aligned拉不高参考时钟未约束正确、CMAC复位顺序错误检查时钟频率、抓取复位顺序参考PG203复位时序重写复位逻辑ping不通ARP未回复、IP地址配置错误抓ARP请求和ICMP回显核对MAC/IP配置检查IP校验和处理小流量通大流量严重丢包用户逻辑消费能力不足、FIFO深度不够ILA观察tready和FIFO占用增加FIFO深度、优化消费逻辑高速率下偶发错误帧未使能RS-FEC查看CMAC状态计数重新配置CMAC打开RS-FEC综合后时序违例组合逻辑过长、复位扇出过大查看关键路径报告插入流水寄存器、使用局部复位5. 移植完成后的经验总结与扩展建议这次移植让我体会最深的一点是开源项目本身的质量和成熟度非常重要但更关键的是你愿不愿意花时间把代码读透。亲手改过一遍数据通路才真正理解为什么100G下所有逻辑都必须做到极致的流水化为什么每个FIFO几乎都要做成遵循tlast识别的包级FIFO而不是简单的字节级FIFO。这些经验不是看文档能学到的一定要自己动手调一次。另外一个值得分享的经验是测试工具链的搭建。我这次用了iperf3加arping加Wireshark的组合覆盖了从L2到L4的各个层次。对于测试UDP丢包率iperf3的-j参数可以提供更细粒度的抖动和丢包统计对比FPGA内部计数器和服务器端收到的包数能快速判断丢包发生在哪一侧。如果你后续想在这个工程基础上继续扩展我觉得有两个方向可以尝试。一是把udp_stack的端口数量扩展成多端口并行实现类似NAT或者多路分发的能力这在网络加速场景中非常实用二是用XDMA或者PCIe硬核把UDP接收的数据搬到上位机DDR里在主机侧完成更复杂的业务逻辑这也是目前许多100G智能网卡的基本架构。还有一个建议是如果你的板卡光模块是QSFP28并且支持分光测试可以考虑买一台支持100G的交换机和专业的网络测试仪来做更严格的性能验证。实验室没有这个条件的话用服务器网卡加iperf3已经足够验证绝大多数功能。至少对我来说98Gbps量级的实测数据已经证明了这套开源方案在100G场景下的实际可用性。
返回列表