我做FPGA网络方向也有七八年了从千兆、万兆一路做到现在的100G说实话100G UDP这套东西板卡贵、IP贵、仿真慢早期想入门真没什么好路子。直到前几年Xilinx官方开源了XUP VVH协议栈加上Muye那套OpenUDP方案也放了出来圈子里才慢慢有了“能用开源方案把100G UDP调通”的共识。这篇文章就是我基于开源方案移植100G UDP到真实板卡、并且完成上板实测的全过程记录包括选型思路、架构设计、Cmac配置、DDR缓存、ARP处理、以及打流测试时踩过的各种坑。如果你正准备接触高速以太网或者手里有带100G硬核的FPGA板子想跑UDP这篇文章应该能帮你省下几周的摸索时间。1. 内容整体设计与思路拆解1.1 为什么选择开源100G UDP方案在做100G UDP之前很多人会纠结一个问题直接用Xilinx或者Intel的官方UDP/IP核不就行了说实话我在项目初期也看过商用方案但最终决定走开源路线核心原因有三点。第一是灵活性。商用UDP/IP核的定义和封装相对固定你只能通过寄存器或AXI接口去适配自己的应用层。但FPGA上做高速网络真正的难点往往在数据通路怎么组织、缓存怎么分配、控制面怎么和业务面解耦。开源方案的核心代码完全是白的哪一拍数据怎么处理、哪一段逻辑占了几个LUT和BRAM都清清楚楚出了问题也方便定位。第二是成本。100G的商用IP授权费不低更别说有些厂商按项目收几个版本迭代下来就是一笔不小的开销。而开源方案比如Xilinx官方的XUP VVH开源协议栈还有GitHub上Muye的open-udp-100g拿来就能用许可证对商业项目也比较宽松。第三是社区积累。这年头做FPGA网络的不再是单打独斗GitHub上相关的issue、commit记录、博客分享非常多很多坑前人已经踩平了。这不是传统“闭门造车”的开发模式能比的。1.2 移植目标的拆解与预期我这次移植的目标平台是带100G以太网硬核的UltraScale系列FPGA外接QSFP28光模块操作系统侧通过PCIe与FPGA交互。说白了就是要把开源UDP协议栈从仿真环境搬到真实硬件上让它能在100G线速下收发UDP报文同时把误码、丢包、时序收敛这些工程指标控制在合理范围内。拆开来看整个工作分为五块100G MAC/PCS/PMA这是物理层到链路层的桥Xilinx这边叫CMAC硬核负责把AXI-Stream接口的数据封装成以太网帧并完成编码、加扰、对齐等物理层处理。UDP/IP协议栈包含IP层、UDP层、ARP处理和校验和计算这是开源方案的核心内容。DDR4缓存与数据调度100G线速下单方向数据流量接近12.5GB/s片上BRAM肯定不够用必须接DDR4而且要认真考虑带宽和延迟。PCIe DMA接口将FPGA收到的数据送到主机或从主机取数据发送这个部分在高速传输场景下作用关键。上板测试环境包括时钟、复位、光模块初始化、寄存器读写、线上调试工具等外围配套。预期目标我分两步走。第一步先用回环模式把协议栈跑通确认IP层和UDP层逻辑没大问题。第二步接真实光模块配合主机的UDP打流工具验证吞吐量和稳定性。这里要提醒一句“移植上板测试”和“开发一套协议栈”是两件事网上很多教程都在教你怎么写UDP但实际上大部分工程场景你不需要从零写你要做的是把开源代码理解透、裁剪好、适配到自己的板卡上。这是我个人认为这次移植最有价值的思路。1.3 开源方案选型对比与决策依据目前活跃的开源100G UDP方案主要有两个方向。一个是Xilinx官方开源的XUP VVH UDP协议栈代码质量普遍不错配套了完整的IP核生成流程和参考设计文档也比较全。另一个是GitHub上的Muye open-udp-100g作者实现了完整的UDP/IP封装与解析逻辑支持ARP、ICMP代码结构相对简洁比较适合二次开发。两个方案我都跑过仿真我的结论是如果你用的是Xilinx平台并且想减少适配工作量优先考虑XUP VVH因为它的MAC层适配、时钟方案、约束文件都是基于Xilinx工具链做的几乎是无缝衔接。如果你需要在任意FPGA平台之间快速移植或者想深入理解UDP/IP协议栈的每一行代码Muye的方案更直观代码量也小一些。但无论选哪个你都要明白“开源”并不等于“拿来就能用”。协议栈只帮你处理了以太网数据怎么解析和封装但它不负责你的业务逻辑怎么接、DDR怎么调度、时钟域怎么隔离这些才是工程中最容易出问题的部分。所以我的建议是先把项目分成网络协议栈和业务应用两层中间用标准接口解耦然后按“底层到顶层、物理到逻辑”的顺序逐步联调。2. 核心细节解析与实操要点2.1 100G数据通路的关键参数计算做100G UDP之前我建议你先坐下来算一笔账搞清楚每一层数据通路的位宽和时钟这样后面调试才不会一头雾水。物理层100G以太网的线速率是103.125Gbps经过64B/66B编码后有效数据载荷率是100Gbps。Xilinx的CMAC硬核输出给用户侧的AXI-Stream接口默认数据位宽是512bit用户时钟频率是322.265625MHz计算方式是100×10^9 / 512 / 10^9 ≈ 322.265625MHz。这个时钟是CMAC的用户侧时钟也是协议栈的主时钟。UDP/IP协议栈内部处理一般也是512bit数据通路和MAC层保持一致。但要注意仿真环境里你用理想时钟源可以直接跑上板之后则必须先保证CMAC的用户时钟稳定否则整条链路都是错的。DDR4带宽100G线速下单方向数据率是100Gbps也就是12.5GB/s。如果同时记录RX方向的两路数据那就需要25GB/s的写入带宽。以DDR4-2400、位宽32bit为例理论峰值带宽是2400×4/8 9.6GB/s注意这里的单位换算是2400MT/s乘以4字节得到9.6GB/s所以在设计数据通路时DDR4必须用足够宽的位宽或足够高的时钟去匹配甚至需要做分页缓存和包级调度否则接收方向很容易因为DDR带宽不足而丢包。这也是很多初做高速项目的人容易忽略的地方光看协议栈逻辑是没用的带宽计算必须前置。2.2 协议栈各模块功能拆解开源UDP协议栈的顶层一般包含这些模块UDP引擎RX/TX各一个、ARP模块、IP层封装/解析、校验和计算、以及输出侧的MAC适配层。以接收通路为例CMAC出来的512bit数据流进入MAC处理解析出以太网头部判断帧类型。如果是IPv4帧就提取IP头检查校验和再解析UDP头最终将UDP载荷写入FIFO或DDR缓存同时把源IP、源端口、目的端口等元数据一并打包给业务层。这个过程看着简单实际上有大量的细节比如边界处理以太网帧长度不是512bit的整数倍最后一拍需要根据帧长度做字节有效掩码处理否则会把填充字节或者下一帧的头部误当成数据。帧间隙处理100G以太网要求帧间隙至少12字节CMAC会负责插入但你的协议栈在发送侧也要保留相应的间隙不能把两帧连续怼进去。校验和计算IPv4头校验和和UDP校验和都有标准算法一般用增量式更新或延迟计算的方式避免逐字节累加带来的长组合逻辑时延。2.3 ARP处理与MAC地址学习机制以太网通信有一个绕不开的环节就是ARP。两个设备要通过UDP通信发送端必须知道对方的MAC地址ARP就是用来干这个的。开源协议栈里一般会有两个角色一个是主动发起ARP请求另一个是响应别人的ARP请求。移植时重点要注意ARP缓存表的管理。协议栈内部一般用BRAM存一张表每个表项包含IP地址、MAC地址和老化时间。老化时间太长设备拓扑变化后还拿到旧MAC太短则会导致频繁的ARP广播浪费带宽。我的经验是老化时间设在20秒到60秒比较合适用于内外网业务基本够用。另外在FPGA这种场景下目标设备的IP和MAC基本都是静态配置的很多协议栈支持“静态ARP表项”把经常通信的对端地址直接固化可以省掉运行时ARP学习带来的不确定性。我在移植时就把几个固定的测试主机地址配成了静态表项效果很明显打流时的首包延迟和稳定性都有改善。这里要插一句当你用iperf3往FPGA打流时如果你的FPGA发不出ARP响应主机就会一直发ARP请求永远进入不了数据传输阶段。这是非常典型的“配置层面原因导致链路不通”的问题。2.4 DDR4缓存与多队列调度协议栈处理完UDP报文后载荷并不会直接丢给用户逻辑而是进DDR4缓存。这里有一个取舍问题是等一个完整的UDP报文收完再搬DDR还是边收边搬边收边搬可以实现更低的延迟但你需要处理跨拍拼接的问题如果包长不是DDR写突发长度的整数倍就要处理好尾部写操作的mask。等完整报文收完再搬逻辑简单可靠但需要额外的片上缓存存放整包数据在100G速率下一个大包比如9K字节的巨型帧需要接近18K BRAM空间多队列并行时片上资源消耗会非常可观。我这次采用的是折中方案小包直接片上缓存直通大包则分片写入DDR4。实测下来这个方案的资源占用和吞吐表现都比较平衡。另外DDR4的调度器我建议要支持多个读写端口至少做到接收通路、发送通路、CPU读写互相独立否则一旦出现争用打流时表现就会不稳定。3. 实操过程与核心环节实现3.1 搭建工程与配置CMAC硬核先用MZVivado建一个RTL工程芯片型号选UltraScale对应的片子。这里唯一要注意的是在IP Catalog里选中CMAC硬核IP选择线速率100G接QSFP28光模块。CMAC的配置界面有几个关键参数需要理解接口模式默认是AXI-Stream数据位宽512bit用户侧时钟322.265625MHz这是最常见搭配。Flow Control一般是none流量控制在上层业务中做而不是MAC层。Preamble处理CMAC默认会插入或去除前导码你只需关心净载荷数据开始的位置即可。RS-FEC100G SR光模块下建议开启RS-FECReed-Solomon Forward Error Correction这能明显提升误码容限。但要注意RS-FEC会增加10G左右的线路速率开销实际有效带宽会略低于线速这是物理层标准决定的不是开发问题。之后要生成CMAC的例化模板把例化代码复制到自己的顶层模块里。这里提醒一个重点是CMAC的复位时序。CMAC硬核的复位包括gt_rxreset_in、gt_txreset_in、rx_core_rst等不同版本的Xilinx IP对复位时序的要求不一样最稳妥的方法是把所有复位信号先置位等待至少10us再释放释放后轮询状态寄存器确认rx_up、tx_up信号拉高再开始传数据。如果不按这个时序来光模块就可能协商不正常连不上链路。3.2 协议栈RTL集成与接口适配拿到开源的UDP协议栈RTL代码之后首先要在工程里新建一个目录专门用来存放开源代码文件同时注意不同文件是否有相对引用的include路径有的话要一并配好。接着把协议栈顶层模块例化到你的顶层设计中。接口适配是重头戏。协议栈一侧的接口和CMAC直接对接走的是axis_mac_rx/tx总线另一侧是用户接口一般是axis_udp_rx/tx或者自定义的接口。你需要根据你自己的业务类型决定这个接口是直接连到PCIe DMA引擎还是经由DDR缓存模块缓存后再连。这块我有几条实战经验基本都是血和泪的教训务必在用户接口上做反压处理。如果业务侧来不及接收协议栈不会有无限的缓存兜底必须通过tready信号把背压传递到MAC层否则数据会悄无声息地丢弃。务必处理好帧长字段的跨时钟域同步。如果你的业务侧和协议栈不在同一个时钟域帧长、帧序号这些伴随信号要跟着数据一起打拍不能只同步数据不复位伴随信号否则帧头信息和数据内容就会错位。发送方向的UDP校验和、IP校验和不能忽略。因为总有一些测试工具会严格检查校验和一旦算错就会被主机协议栈默默丢弃表现出来就是“发了数据但对方收不到”这种问题排查起来很费时间。3.3 时钟与复位系统的搭建高速FPGA设计里时钟和复位系统基本上决定了这个设计能不能稳定跑起来重要性甚至超过业务逻辑本身。我在这个工程里搭建了三个主要时钟域CMAC用户时钟322.265625MHz给协议栈和MAC侧逻辑使用。DDR4用户时钟300~400MHz给DDR控制器接口逻辑使用。PCIe时钟250MHz或用户时钟频率给DMA引擎和主机交互使用。不同时钟域之间使用异步FIFO或者基于AXI-Stream的同步器进行数据交递。关于异步FIFO我建议直接用Xilinx的FIFO IP设置成独立时钟模式位宽512bit深度根据你的吞吐需求来定。注意异步FIFO的复位最好用同步释放的复位信号而且在FIFO复位期间不要进行读写操作否则容易产生亚稳态导致数据损坏。复位方面我遇到一个印象很深的坑CMAC的复位信号如果在上电初始化没做好会导致光模块link一直协商不上误码率激增。后来仔细看文档发现CMAC要求GT参考时钟稳定之后才能释放复位所以我额外做了一个“参考时钟锁定检测 延时释放”的逻辑用CPLL或QPLL的locked信号作为复位释放的前提条件问题就解决了。这个细节还不算冷门但一旦碰上确实很难受。3.4 上板前仿真验证要点很多人上板前只在Vivado里跑一遍behavioral simulation就完事了其实这对高速链路项目来说远远不够。我个人的经验是至少需要跑三个层级的仿真才能大概率保证上板一次跑通。第一个是协议栈单元级仿真。用testbench给协议栈喂一些标准的以太网帧检查UDP解帧输出是否符合预期。可以用Wireshark抓包转成hex数据作为仿真激励这样更贴近真实报文。第二个是MAC与协议栈联仿。例化CMAC的仿真模型把CMAC收发的数据流完整跑一遍重点看帧间隙、错误标志、状态信号是否异常。第三个是端到端的系统级仿真。这一步可以把PCIe、DDR模型都加进去用主机模拟器发送一个UDP包看它能不能正确的从PCIe侧出去经过DDR缓存、协议栈、CMAC再到光模块。仿真过不了就急着上板是FPGA开发里最浪费时间的操作。仿真时把校验和检查、帧长检查这些断言加倍严格一次仿真能发现的错误比上板调三天主机抓包能发现的错误多得多。3.5 上板测试方案设计上板之前要想清楚你测试的目的是什么。我只关心两个指标能不能通、通了能跑多快。因此我的上板测试方案分为三个阶段第一阶段是链路连通性测试。先用一根光纤把FPGA和主机网卡直连配置好静态IP然后从主机ping FPGA。如果ping通说明ARP、IP、ICMP这些基础协议没问题。如果ping不通先查光模块link状态、检查MAC地址配置、检查ARP缓存表。第二阶段是UDP回环测试。在FPGA内部做一个发送端口到接收端口的回环把主机发过来的UDP报文回发。这样能验证协议栈的完整收发通路。这时候可以用Wireshark在主机侧抓包确认往返延迟、包内容是否一致。第三阶段是满带宽打流测试。使用iperf3或者自研的打流工具向FPGA发送满负荷的UDP报文查看丢包率和带宽。这是整个移植工程最关键的一关能在这一步通过的方案才是可用的方案。4. 常见问题与排查技巧实录4.1 链路协商失败光模块link一直down这是最常见的问题原因可能有这么几类光模块没插紧或者损坏、光纤接反、光模块协商速率不匹配、CMAC复位时序不对。我最开始遇到这个问题时在硬件和光纤上排查了很久后来发现是CMAC的复位时序不满足要求。具体表现是gtpowergood信号迟迟不拉高导致transceiver没有正常工作。解决方案是重新梳理复位逻辑上电先保持所有复位有效等gtpowergood稳定输出高电平再释放复位之后再轮询CMAC状态寄存器直到rx_ptp_lock和tx_ptp_lock都拉高才认为链路就绪。4.2 1500字节小包吞吐正常大包持续丢包这个现象很有迷惑性board上有时候小包吞吐能做到线速换成大包就疯狂丢包很多人一开始会以为协议栈处理不过来大包。但根据我排查的经验这种问题往往出在DDR带宽或者缓存管理策略上。100G单方向线速是12.5GB/s如果你用的DDR位宽或者频率不够大包写入DDR的带宽就会成为瓶颈。你可以先做个简单的读改写测试确定DDR的最大有效带宽。如果你的DDR总带宽只有8GB/s那么即使空闲线速流量进来也会丢包。这时候要么换更高位宽的DDR要么在协议栈和DDR之间加一层包缓存用片上BRAM在小包场景下做旁路把DDR的压力降下来。另外还有一个容易忽略的问题就是DDR控制器的bank冲突。频繁地在不同bank之间读写会造成激活和预充电的额外开销实际带宽可能远低于理论值。这个问题可以通过调整DDR4地址映射方式来解决比如把高bit位做bank地址或者设计时让同一方向的数据尽量写入同一个bank。4.3 主机ping不通FPGA但FPGA能收到ARP请求这种情况多半是ARP响应逻辑有bug。把你的ARP表、MAC地址过滤逻辑都认真检查一遍。有一个非常容易被忽略的地方就是以太网帧的目的MAC地址过滤参数协议栈内部一般都有一个“本机MAC地址”的寄存器或参数如果这个值和实际网卡的MAC不一致协议栈就会把发给自己的报文当成无关流量直接丢弃。还有一点100G网卡都有MAC地址过滤表你必须在主机网卡上把FPGA网卡的MAC地址添加进去否则主机网卡在硬件层就把FPGA发来的ARP应答帧丢掉应用层根本看不到。这个问题在服务器网卡上尤其突出很多型号默认只接收本机MAC的帧。4.4 打流时的随机微丢包吞吐测试的时候如果出现偶尔丢一个包但在可接受范围内该怎么判断是设计问题还是外部链路问题我的排查方法是同时看FPGA侧的关键计数器UDP RX帧计数、UDP TX帧计数、DDR写入字节数、DDR读出字节数、以及协议栈内部各级FIFO的溢出标志。如果溢出标志只在某个FIFO上出现就说明那一级的背压或者调度有问题。如果所有FIFO都没有溢出但依然丢包那就要看物理层的误码。检查CMAC的CRC错误帧计数以及RS-FEC的symbol error计数如果这两个计数持续增长那就是光模块或光纤质量的问题和你的逻辑设计无关。我遇到过一种情况是光纤弯曲半径太小导致的误码率升高重新布光后问题就消失了。4.5 PCIe侧回压导致的级联丢包我的架构里业务数据从DDR读完以后通过PCIe DMA送给主机。如果主机侧DMA带宽不够那么DDR读出侧会反压到协议栈的发送接口这时候如果发送FIFO满了新的UDP报文就只能丢弃。这个问题要区分是偶发的背压还是持续的带宽不足。偶发背压可以通过加大FIFO深度来吸收持续带宽不足就要考虑DMA描述符数量、中断合并策略、以及PCIe的Max Payload Size配置。其实很多时候不是FPGA跑不到100G而是主机端没有准备好接收100G数据的能力这个锅不能全甩给FPGA。5. 上板测试结果与性能数据实录这次移植我跑了几组测试简单记录如下给后来者一个参考。回环测试中用主机发送1500字节UDP包FPGA回发同样内容Wireshark确认上下行包序一致无重新排序往返延迟稳定在2us以内。这意味着协议栈的基本收发通路没有问题CMAC和光模块链路也正常。满带宽打流测试中使用iperf3 UDP模式从主机向FPGA发送1400字节UDP载荷FPGA侧接收并统计后显示线速约96Gbps考虑到帧间隙和UDP头开销这是正常现象丢包率低于0.001%。如果换成巨型帧默认MTU 9000实际有效吞吐还能进一步提高接近线速。持续稳定性测试中满负荷运行4小时CPU占用率稳定FPGA侧资源占用有少量余量内部FIFO的溢出计数一直为零。这个结果说明在长期工作下时钟和复位系统稳定协议栈不会因为偶发问题积累而崩溃。主机端性能数据也值得记录一下在普通x86服务器上使用单条PCIe Gen3 x16通道通过DPDK收包时100G大流量下无丢包。但用Linux内核协议栈直接收包时CPU会成为瓶颈100G速率下部分帧会因处理不过来而丢失。这里要特别提醒100G UDP测试无论FPGA侧还是主机侧都需要配合DPDK或专用收包工具否则会得出虚假的丢包结论。6. 一些我们在开发中容易忽视的细节最后分享几个在上板调试中被反复验证过的经验每个都对应过真实问题建议你存下来。第一次上板先把CMAC回环打开。CMAC内部有near-end loopback模式可以不经过光模块直接回环先把MAC层到协议栈的路径跑通再开外部回环能大幅缩小排查范围。100G网卡默认开启RSSReceive Side Scaling多队列散列可能导致UDP包乱序。如果你对包序敏感要在主机侧把RSS关闭或者在FPGA内部做包序号检查来确认。时钟约束一定要把CMAC用户时钟设为true path否则上板跑久了容易偶发亚稳态。很多工程在仿真中完全正常上板后偶尔出现几个乱码就是这个原因。保存寄存器映射表的时候顺带把DEBUG接口也加上。建议在协议栈关键计数器和FIFO状态上全部拉出调试端口用ILA或者逻辑分析仪实时观察。别嫌占资源一次调试省下的时间远超过那点资源。从开源代码到真正能在100G线速下跑UDP中间的距离比你想象中要大。协议栈本身只是一部分时钟复位、DDR带宽、PCIe以及和主机侧的配合每一环都会决定最终效果。希望这篇文章能帮你少走几步弯路回头等你调通了自己的100G UDP链路你会觉得这趟折腾是值得的。