FPGA实现100G UDP协议栈:verilog-ethernet移植与上板验证全记录
最近把一套开源的100G UDP协议栈完整跑通了移植加板上验证从拿到代码到上板实测前后折腾了差不多两周。今天就把这事写透把那些文档里从来不写的坑也一起倒出来给后面要搞FPGA高速网络的朋友少走点弯路。先说清楚这个项目是干什么的在FPGA上实现一个100Gbps的UDP网络通路让应用能从光口收包、解析、做转发或者计算处理再发出去。为什么强调UDP而不是TCP原因后面细说。开源方案我选了verilog-ethernet这套库MIT协议、代码风格干净、支持10G/25G/100G多速率、社区用的人多拿来改造成本最低。整套东西适合正在做FPGA高速接口、做网络卸载、或者搞信号采集和图像传输加速的工程师参考哪怕你之前只用过UDP网络调试工具、没碰过100G看完也应该能把整条链路怎么搭理清楚。这次移植的平台是Xilinx UltraScale系列工具链用的Vivado 2022.1板卡是一块带QSFP28光口的FPGA开发板对端用Mellanox的100G网卡做打流和抓包验证。后面所有内容都是这个环境下的真实操作记录不同平台会有差异但排查思路和设计逻辑是通用的。1. 内容整体设计与思路拆解1.1 项目背景100G时代FPGA高速网络到底难在哪先说结论100G在FPGA上跑起来难度不在“100G”这个数字本身而在于整个数据链路每一环都不能有短板。我之前做过不少10G/25G的接口项目按惯性思维以为100G就是把25G乘4、数据位宽乘4而已。实际上手才发现完全不是这么回事。100G以太网用的是4条25.78125Gbps的SerDes lane加起来线路速率103.125Gbps编码方式用64B/66B比10G的64B/66B那是10GBASE-R在PCS层的对齐、加扰、FEC处理上复杂度高了一个量级。再加上用户逻辑侧的接口通常是512bit 322.265625MHz这个频率对于FPGA逻辑来说已经不低了布线稍不注意时序就收敛不了。典型场景很明确数据中心里CPU处理100G UDP报文光是中断和拷贝就能吃掉好几个核所以现在DPU、智能网卡都喜欢把网络数据面卸载到FPGA上。FPGA做这件事的优势是延迟固定、吞吐线速、功耗可控而且数据从光口进来之后可以直接往DDR、HBM或者计算单元里送不需要经过内核协议栈。这个项目本质上就是把这块“卸载能力”的核心骨架搭起来。1.2 为什么选UDP而不是TCP这是我被问得最多的问题。很多人一上来就问能不能把TCP也移植了我的回答通常是能但没必要而且非常痛苦。TCP在硬件上实现要处理的东西太多了连接状态机、序列号管理、滑动窗口、重传队列、拥塞控制、RTT估算、ACK延迟确认……这一套东西如果全部用RTL写出来代码量至少是UDP栈的十倍以上而且验证周期极长。市面上能做TCP Offload EngineTOE的公司屈指可数不是没有原因的。反观UDP它的头只有8字节无连接、无状态硬件上做解析和组包非常简单延迟可控吞吐容易跑满。在很多真实的FPGA高性能场景里可靠传输根本不需要在协议层解决数据采集场景丢一个包就重发整帧或者靠前向纠错分布式训练场景有更上层的检查点机制兜底视频传输场景丢几帧人眼根本感知不到。所以“UDP 上层应用兜底”是FPGA高速网络的默认架构先把数据面打通可靠性再在业务层解决。顺带提一句如果确实需要硬件TCP建议直接考虑商业IP核或者往RDMA方向走自己从零写TCP卸载纯粹是给自己挖坑。1.3 开源选型为什么盯上verilog-ethernet这套库开源世界的FPGA以太网方案其实不多能跑100G且维护活跃的就更少。我评估过的选项主要有三个自己从零写MAC和UDP栈工作量巨大光是MAC层的PCS对齐、CRC校验、FIFO管理就够喝一壶时间成本完全不可控。商业IP核Xilinx/Altera的MAC和UDP IP都很成熟性能有保障但价格不便宜而且代码不开源出了问题只能提Case等回复迭代很被动。verilog-ethernet开源库MIT协议代码完全开放可以按需裁剪内部逻辑看得见摸得着。最后选了verilog-ethernet。这套库的架构我很喜欢它把以太网从MAC层往上拆成了多个独立模块10G/25G/100G MAC、ARP缓存、ICMP回显、UDP卸载、TCP卸载模块之间统一用AXI4-Stream接口连接裁剪和替换非常灵活。设计者Alex Forencich是硬件开源圈的老面孔代码质量在同类项目里属于第一梯队时序风格保守但好用仿真环境和测试代码也比较完善。不过说句实在话拿开源代码不代表可以无脑用。移植上板之前你必须把每一级模块的内部时序吃透否则出了问题你连从哪儿开始查都不知道。后面我会把核心模块逐个拆开讲。2. 核心细节解析与实操要点2.1 100G数据通路架构拆解一个完整的100G UDP通路从光口到用户逻辑物理上要经过这么几层QSFP28光模块/光缆 → PMD物理介质相关子层→ PMA物理附着子层→ PCS物理编码子层→ MAC介质访问控制子层→ 网络层ARP/ICMP/UDP解析→ 用户应用。在Xilinx平台上有两种做法一种是直接用FPGA内部的100G硬核CMAC另一种是纯逻辑实现软核MAC。verilog-ethernet对这两种方式都给了支持。硬核CMAC的优势是经过硅验证、时序好、占用逻辑少、支持RS-FEC劣势是配置寄存器多、使用起来复杂软核MAC的优势是灵活可改、不依赖特定芯片劣势是时序收敛压力大、功能完整度需要自己验证。这次移植我选了CMAC硬核方案原因很简单100G的PCS/PMA层逻辑非常繁琐用软核实现意味着要把大量精力花在物理层调试上而上板验证的目标是UDP协议栈本身。用硬核把物理层问题隔离掉可以更聚焦在协议栈的正确性上。下面是硬核和软核的对比对比项硬核CMAC开源软核MAC时序收敛难度低硬核已优化高322MHz逻辑设计要花功夫配置复杂度高寄存器/回环/Rx tuning多低接口直接RS-FEC支持支持取决于实现通常不完整逻辑资源占用极少很大BRAM/LUT消耗明显灵活性低行为由硬件决定高可深度定制适合场景产品化、高性能教学、软核研究、差异化实现在verilog-ethernet里MAC层之上就是标准的AXI4-Stream接口。100G下这个接口的典型配置是512bit数据位宽tkeep信号64bit时钟大概在322MHz附近。所有上层模块ARP、ICMP、UDP都从这个接口收发帧理解这条AXIS总线是关键中的关键。2.2 MAC层与CRC最容易翻车的地方MAC层最容易被低估的是FCS帧校验序列处理。FCS本质是CRC3210G下用64bit位宽算还好到了100G的512bit位宽一个时钟周期要处理512bit数据的CRC就不能用简单的串行LFSR硬算了得做CRC矩阵的并行展开或者把一个周期拆成多级流水。verilog-ethernet库里已经做了并行CRC但移植时最容易踩的坑是发端在组帧时把CRC放到哪儿、收端在校验时从哪儿开始算、以及tuser信号上有没有正确标记CRC错误。我见过不止一次因为帧边界差一个字节导致对端一直报FCS错误、链路能通但ping不通的情况。除了CRCMAC层还有几个细节必须注意前导码和SFD标准以太网帧开头是7字节前导码加1字节帧起始定界符这些在CMAC内部通常会处理好。如果用软核MAC千万记得这部分是有开销的。IFG帧间隙100G以太网要求帧与帧之间至少留出一定的空闲周期回压和流控逻辑不能把IFG吃掉否则对端会当错误帧处理。填充字段小于64字节的短帧要补0到64字节CRC是对补齐后的数据计算的漏了这一步会出现诡异的小包错误。tkeep信号AXIS总线上不是每个周期都是满数据tkeep指示哪些字节有效。做位宽转换或者跨时钟域时tkeep没跟着走会导致数据错位这类bug非常隐蔽。提醒一句如果你在仿真里看到CRC错误不要急着怀疑算法。先检查你的testbench是不是正确模拟了帧间隙和前导码很多“CRC错误”其实是激励本身就不符合以太网规范。2.3 跨时钟域与位宽转换322MHz下的512bit总线100G MAC这一侧是512bit 322MHz而用户逻辑、DDR控制器、PCIe接口往往在不同的时钟域和位宽下工作所以跨时钟域FIFO是标配。verilog-ethernet推荐用Xilinx的异步FIFO IP配置成AXI4-Stream模式数据位宽和深度按需设置。这里要特别注意FIFO的水位设计。AXIS FIFO的almost_full信号要提前拉高给对端留出反应时间。如果等FIFO真正满了再回压对端流水线里已经在传输的数据就会溢出丢包。实际操作中almost_full的阈值一般设在FIFO深度的75%~85%具体值取决于回压路径的延迟需要反复调。位宽转换同样容易踩坑。100G的512bit到用户逻辑的64bit/128bit不是简单地把数据截断拼接就行必须同时处理tkeep和tlast。verilog-ethernet里提供了一个通用的位宽转换模块但它在处理帧末端的部分填充字节时逻辑比较复杂移植时建议先用仿真把“非对齐小包”场景跑透。什么叫非对齐小包就是一个UDP报文只有70字节首尾都落在不完整的AXIS传输里这时候tkeep的处理稍有不慎就会让整帧错位。这类问题在仿真里最容易暴露不要跳过。2.4 ARP和ICMPUDP能通的前提条件很多人移植UDP栈之后第一件事就是拿UDP调试工具发数据结果发现PC那边ARP解析不出来整个链路处于“看起来活着但实际不通”的状态。原因很简单以太网是靠MAC地址通信的PC要知道FPGA的MAC地址必须先发ARP请求FPGA必须能响应ARP。verilog-ethernet里的ARP模块做得比较完整会自动学习收到的ARP请求并把应答发回去同时维护一个简单的ARP缓存表。移植时需要注意缓存表的老化时间——如果老化太短高频通信时会频繁触发ARP重新解析影响吞吐如果太长MAC地址变了之后会一直发到错误的目的地。实测下来老化时间设在30秒到60秒比较合理。ICMP回显也就是ping是验证二层和三层的利器。FPGA里实现ping要比实现UDP稍微麻烦一点因为ICMP报文里有个校验和需要计算。verilog-ethernet的ICMP模块对收到的echo request计算响应用的是增量校验和算法。这里有一个细节ICMP校验和覆盖整个ICMP报文计算时要先把校验和字段置零算完再填回去。不要直接拿收到的报文改个type就发回去校验和要重新算。2.5 UDP解析与组包的实现要点UDP模块是整个协议栈的数据面核心。verilog-ethernet的UDP实现分收和发两个方向接收方向的核心逻辑是从AXIS流里识别出IPv4/UDP报文解析以太网类型字段0x0800、IP协议字段17、目的端口号然后把UDP的payload剥离出来通过AXIS接口送给用户逻辑。这个过程中还要检查IP头部校验和、UDP长度字段是否和实际数据长度一致不一致的要丢弃或者打错误标记。发送方向的核心逻辑是用户逻辑把payload送进来硬件自动加上UDP头、IP头、以太网头计算IP校验和然后往MAC层发。这里有个容易忽略的点UDP头部的长度字段是“UDP头 payload”的长度IP总长度字段是“IP头 UDP头 payload”的长度这两个字段很容易填错填错了对端wireshark抓包会报checksum错误或者直接丢包。另外UDP的checksum在IPv4下是可选的IPv6下是必选的。verilog-ethernet的默认配置是发送时不计算UDP checksum置0接收时也不校验。这在绝大多数场景下没问题因为以太网的FCS已经保证了物理链路上的数据完整性UDP checksum主要防止的是IP层路由过程中的位翻转实际应用里很少碰到。但如果你的数据要跨越复杂的路由网络建议还是把UDP checksum打开。3. 实操过程与核心环节实现3.1 硬件平台与工具链准备这次用的板卡是带VU3P器件的开发板板载QSFP28光口外接一个Mellanox ConnectX-5双口100G网卡做对端。Vivado用的2022.1版本工程里用到的关键IP有CMAC硬核IP100G带RS-FEC选项AXI4-Stream异步FIFO两个一个收一个发时钟管理IP从QSFP28 recovered clock或者板上独立时钟生成322MHz用户时钟如果手上没有100G板卡用Alveo U200/U250也可以流程几乎一样只是板卡约束和CMAC的引脚位置不同。对端网卡建议用Mellanox或者Intel的100G网卡驱动和工具链都成熟iperf3和Wireshark都能直接用。光模块方面短距离用DAC线缆最省事不需要光模块和光纤插上就能用长距离再用QSFP28光模块加光纤。3.2 RTL代码移植与工程集成从GitHub把verilog-ethernet仓库clone下来之后不要急着把所有文件全加进工程。先看examples目录下的fpga_core那里有参考的集成方式。我这次移植的大致步骤第一步新建Vivado工程器件选对。然后把仓库的rtl目录下需要用到的那几个文件夹添加进来lib公共的FIFO、位宽转换、CRC、mac、ip、udp、arp、icmp。注意lib里的很多模块是参数化的有些用了Xilinx原语有些用了通用逻辑工程设置里要把XILINX相关宏定义配好。第二步实例化CMAC IP。按照fpga_core里的方式把CMAC的AXIS接口连接到verilog-ethernet的100G MAC适配层。这里有个容易晕的地方verilog-ethernet的100G MAC既可以直接连CMAC原生接口也可以通过一个适配层转成标准的MII风格接口。建议直接用它的cmac adapter可以减少很多手工对齐工作。第三步编写顶层模块把CMAC、FIFO、ARP、ICMP、UDP和用户应用串起来。用户应用我建议先做最简单的东西收方向把UDP payload里的数据按照固定格式解析发方向把收到的数据原样发回去回环模式或者做一个计数器周期发包。先把数据面跑通再往里面加自己的业务逻辑。第四步写约束文件。除了板卡的引脚约束重点关注时钟约束和跨时钟域约束。CMAC的recovered clock、user clock、slow clock如50MHz参考时钟都要正确约束否则时序报告会一片红。异步FIFO两侧的时钟要设成asynchronous group或者用set_clock_groups声明不然Vivado会在CDC路径上报大量违例真正的问题反而被淹没。3.3 仿真验证上板前别偷懒移植完成后直接上板是新手最容易犯的错误。100G系统一旦上了板调试手段就很受限ILA虽然能抓信号但抓深度的限制、触发条件的复杂度都会让定位问题变得极其痛苦。所以我的习惯是仿真不过关绝对不上板。仿真环境用verilog-ethernet自带的testbench做基础然后自己补了几个关键用例单包回环测试PC发一个特定payload的UDP包FPGA收到后原样发回检查内容一致。连续多包压力测试模拟PC以线速发送大量不同长度的报文验证FIFO水位和回压逻辑是否正常。重点测64字节小包这是缓冲最容易爆的场景。ARP交互测试先发ARP请求验证应答内容里MAC地址和IP地址是否正确再验证ARP缓存学习后能否直接通信。错误注入测试人为制造CRC错误帧和长度异常帧确认接收路径能正确丢弃并上报错误计数。仿真通过之后别急着上板先看一下资源利用率和时序报告。100G设计跑到322MHz不是理所当然的如果关键路径时序余量小于100ps上板之后大概率会偶发丢包或者CRC错误。时序不过就先加流水线级数再考虑综合策略和布局优化。3.4 上板实测从ping通到100G线速打流硬件连接和bitstream下载这些常规操作不细说重点说测试流程第一步IP地址规划。PC网卡设192.168.1.1/24FPGA侧固定192.168.1.10MAC地址用verilog-ethernet默认的或者自己编一个保证和局域网内其它设备不冲突。第二步PC端配置网卡。这步很关键很多人忽略。要关闭网卡的LSO/LRO大包卸载、关闭RSS接收端缩放至少要把ring buffer调到最大否则100G流量一来CPU直接被打爆。命令方面ethtool -g看当前ring大小ethtool -G eth0 rx 4096 tx 4096调大ethtool -K eth0 gro off gso off关掉卸载。如果是普通PC当打流机这一步几乎决定了你能不能看到线速。第三步ping验证。ping 192.168.1.10不通就先查ARParp -n看有没有把IP解析成FPGA的MAC没有说明ARP模块有问题通了说明二层三层通了。第四步iperf3 UDP打流。PC作为客户端往FPGA发UDP包FPGA侧做回环回环包再打回PC。命令我实测用这条iperf3 -c 192.168.1.10 -u -b 0 -t 30 -l 1472 --reverse-b 0表示不限带宽-l 1472是UDP payload大小1500字节MTU去掉20字节IP头和8字节UDP头--reverse表示反方向测试。测完看两边的吞吐和丢包率。如果吞吐接近线速且丢包很低说明用户逻辑处理能力和FIFO带宽没有瓶颈如果大量丢包先把用户逻辑的回环模式打开不做解析直接转发排查是协议栈的问题还是用户逻辑的问题。第五步Wireshark抓包确认报文格式。在PC上抓包确认FPGA发回来的报文里以太网头、IP头、UDP头的字段全部正确checksum没有报错。这里有个小技巧Wireshark里用udp.port 你的端口号做过滤条件可以快速区分FPGA的报文和其它网络流量。不同包长下实测的吞吐数据大致是这个水平回环模式包长字节理论线速上限实测吞吐丢包率64~59Gbps56.2Gbps0.01%256~88Gbps86.5Gbps0.01%1500~95.7Gbps94.8Gbps0%9000~98.5Gbps97.9Gbps0%短包跑到不到线速是正常的因为帧间隙和前导码在小包场景下占比很高。4. 常见问题与排查技巧实录4.1 链路起不来或者协商失败上电后首先看CMAC状态寄存器确认PCS/PMA层有没有完成链路训练和对齐。如果CMAC报link down一半以上的原因出在物理层DAC线缆没插紧、光模块没插到位、对端网卡没起来。剩下的一半里要重点看两端是否都启用了RS-FEC。FEC不匹配时不仅link可能起不来就算起来了也会出现随机CRC错误。排查顺序建议从物理层到逻辑层先用IBERT误码仪测试眼图确认SerDes通道的信噪比没问题再做CMAC回环确认硬核本身正常最后才回到FPGA逻辑。直接一上来就怀疑逻辑代码很容易在没问题的方向上浪费大量时间。4.2 链路通了但ping不通这是我最常被问到的问题。链路通了说明物理层和MAC层工作正常ping不通问题出在网络层以上。第一步看FPGA侧的ARP表状态如果PC一直发ARP请求但FPGA没响应用ILA抓ARP模块的接口看请求帧有没有解析到、应答帧有没有发出去。第二步看PC端ARP缓存arp -n确认IP和MAC映射是否正确如果映射错了用arp -d清掉缓存重试。第三步看IP层的校验和尤其要注意IP总长度字段的计算这个字段错了会导致整个包被对端静默丢弃。还有一个常见原因PC网卡的防火墙拦截了ICMP回显请求。Windows和大部分Linux发行版默认都允许ping但有些服务器系统默认拒绝先ping网关或者其它主机排除系统防火墙的因素。4.3 CRC错误数量居高不下如果CMAC状态显示rx_crc_error一直增加优先怀疑物理层信号质量问题。我之前遇到过一次是因为DAC线缆质量太差长距传输信号劣化严重换成短一点的高质量线缆后CRC错误立刻清零。其次怀疑两端FEC配置不一致特别是100G SR4光模块场景下一端开了FEC另一端没开CRC错误率会在某个流量阈值后突然飙升。先看CMAC寄存器里rx_crc_error和rx_fec_corrected计数器的增长速度能区分是通道误码还是逻辑问题。逻辑侧的CRC错误通常集中在位序和接口宽度上可以在ILA里抓CMAC的rx_axis_tdata和rx_axis_tuser信号看错误标记是不是总是落在某些特定位置如果是八成是FCS在数据通路里被移位了。4.4 iperf3打流吞吐上不去吞吐上不去的原因按照概率从高到低排查第一PC端网卡配置不对。ring buffer太小、中断亲和性没绑定、RSS没关导致多队列乱序这些都会让CPU成为瓶颈。用ethtool -S eth0看rx_missed和rx_drop的数量几乎能立刻判断是不是网卡侧在丢包。第二FPGA侧FIFO或者用户逻辑来不及处理。检查AXIS总线的tready信号如果长时间拉低说明下游处理能力到顶了。这种情况要么加深FIFO要么优化用户逻辑的流水线减少气泡周期。第三端点之间的握手开销。标准的AXIS协议每传一拍数据都需要tvalid/tready同时有效如果逻辑里tvalid的拉高时间太短实际有效带宽会大打折扣。建议在关键路径上做流水线寄存让核心数据通路保持每拍都能传输。4.5 小包场景吞吐骤降64字节小包场景下吞吐理论上限只有59Gbps左右很多人以为这是bug其实是物理限制。每个64字节的小包在以太网上实际要占用84字节的传输时间含8字节前导码和12字节IFG再加上以太网最小包长限制协议开销占比非常高。遇到这种需求考虑两个方向要么做多包合并packet coalescing把多个小包攒成一帧再传输要么把业务逻辑层面的大包切分策略调整一下减少无效包的数量。4.6 时序收敛困难100G设计在322MHz下综合时序不收敛是很常见的事。调整优先级从高到低先检查路径上有没有跨时钟域的异步FIFO被错误约束成了同步路径这条最坑再检查综合策略把综合选项里的retiming打开然后给核心数据通路加流水线寄存器verilog-ethernet的模块很多地方都有pipeline参数可以直接打开。实测下来这三板斧能让大部分关键路径从违例变成收敛。5. 移植与测试过程中的补充心得最后再分享几个这次项目里印象比较深的经验。第一部分是关于开源代码的使用心态。verilog-ethernet这套库虽然代码质量很好但它不是一个“开箱即用”的黑盒。你拿到的是一堆通用模块真正的业务逻辑要自己写参数的适配要自己调跨时钟域的方案要根据实际硬件环境决定。不要指望git clone之后几个小时就能上板跑通这玩意儿跟标准软件开发完全两码事。第二部分是关于测试工具的配合。要验证100G UDP链路光靠FPGA侧的逻辑分析仪是不够的PC端的工具链要配合好。iperf3负责压力测试和吞吐验证Wireshark负责协议细节的确认ethtool负责网卡状态的监控三个工具配合起来才能快速定位问题。如果调试过程里发现现象和预期不符优先怀疑测试环境本身而不是FPGA逻辑这是我踩过很多次坑之后得出来的血泪经验。第三部分是“链路通了”不等于“协议栈对了”。CRC正确的报文也可能被上层模块错误解析ARP能通也可能只是缓存表刚好命中。我自己的习惯是每移植一层就验证一层MAC层看FCS计数IP层看校验和UDP层看端口和payload内容每层都用仿真和上板双重确认再进入下一层。这种按层推进的方式虽然慢一点但每一步都走得踏实最后联调的时候反而最快。第四点是关于回环测试的陷阱。用回环模式验证的时候FPGA内部的收发路径都跑了但是PC到FPGA之间的单向路径问题可能被掩盖。做过一次单向验收之后强烈建议把回环拆开单独验证FPGA收、FPGA发两条路径分别用iperf3的不同方向来测试。我在测试中就遇到过FPGA发送方向没问题、但接收方向因为FIFO读侧逻辑有bug导致大量丢包的情况这种问题在回环模式下非常容易被忽略因为收发两边的坏包会互相掩盖。这次项目从开始移植到最终跑满线速大概花了两周其中一半时间在调时序和排FIFO问题四分之一时间在折腾PC端网卡配置和测试工具真正跑通协议栈只用了不到两天。这分布很能说明问题100G FPGA UDP这套东西难的不是UDP本身而是整个系统的协同工作。每一环都验证扎实了最后的结果自然而然就出来了。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →