
1. 为什么我决定把100G UDP协议栈搬上FPGA以及踩坑前的现实说实话第一次接到开源100G FPGA UDP移植上板这个需求时我内心是有点打鼓的。之前做过不少10G、25G的UDP传输方案也算熟门熟路但100G完全是另一个量级——不仅仅是速率翻了几倍而是整个数据通路的设计哲学都要跟着变。更刺激的是这次要求基于开源方案做移植不能像以前那样直接拿商用IP核一把梭。先说结论100G UDP在FPGA上跑通和10G/25G UDP跑通表面上看都是把UDP报文发出去、收回来但真正落地时会发现瓶颈几乎从来不在协议本身而在MAC层与用户逻辑之间的带宽匹配、时钟域跨越、以及DDR4缓存调度的细节。这篇文章不会讲太多理论重点把移植过程中的真实路径、测试方法和几个印象极深的坑记录下来。先聊聊背景。UDP协议本身极简——没有连接管理没有重传机制头部固定8字节所以它在FPGA里的实现核心就是两条流水线发送方向用户数据 → UDP打包 → IP打包 → MAC帧 → 线缆和接收方向线缆 → MAC解析 → IP/UDP解析 → 用户数据。10G或25G时代常用做法是用Xilinx的10G/25G Ethernet MAC硬核或者软核配合简单的状态机处理ARP和ICMP就够用了。但到了100GMAC层通常使用CMAC或者100G Ethernet IP核接口从单路64bit变成了多通道512bit甚至1024bit用户逻辑必须适应这种宽位宽、高频率的数据通路。开源方案里比较成熟的还是Alex Forencich 的 verilog-ethernet 项目它支持10G/25G/100G代码风格干净、模块化做得很好社区活跃度也高。这次移植就是基于这套代码做的二次开发。项目目标是实现一个可以实际跑业务的100G UDP通路接收侧能从光口收UDP数据解包后写入DDR4发送侧能从DDR4读出数据、打包成UDP发出去。同时还要支持ARP响应和ICMP ping方便用软件侧做基础连通性测试。整体架构用的是Xilinx VCU118开发板也有用AU200的后面会说到差异FPGA型号是Virtex UltraScale VU9P。在进入细节之前先把这个项目涉及的几块主要技术内容列出来方便各位对照自己的需求看这篇笔记开源协议栈适配verilog-ethernet的模块裁剪与接口适配100G MAC层选择CMAC硬核 vs 第三方开源MACAXI4-Stream接口的速率匹配与反压处理用户侧DDR4缓存设计多通道读写仲裁上板测试方法论ARP、ICMP、UDP打流、误码排查踩坑记录跨时钟域、位宽转换、时序收敛、回环测试陷阱2. 开源协议栈的选型依据以及与商用IP核的实质差异2.1 为什么是verilog-ethernet而不是其它开源项目开源以太网协议栈在FPGA圈子里有几个选项除了Alex Forencich的verilog-ethernet还有Corundum这个更偏完整NIC的开源方案。Corundum确实功能更强支持PCIe、DMA、多队列设计目标就是一个真正可用的网卡。但如果你的项目只需要UDP收发、不涉及PCIe host接口Corundum的复杂度反而是负担——它的学习曲线陡得多代码量的维护压力也大。verilog-ethernet的定位刚刚好它是一套可裁剪的Ethernet组件库从MAC、MII/GMII/RGMII/XGMII/CMAC适配到IP、ARP、UDP、TCP都有现成模块。每个模块的接口风格一致都是标准的AXI4-Stream加一个简单的控制接口拼起来非常方便。具体到100G场景verilog-ethernet提供了一套eth_mac_100g相关的前端适配逻辑它本身不直接实现100G PCS/PMA那个必须依赖FPGA原生的CMAC或者第三方高速SerDes IP而是把CMAC的接口通常是Xilinx CMAC硬核的axi_cmac接口或者mii_100g接口转换成内部的AXI4-Stream格式。这个设计很聪明把MAC层里跟协议相关的部分和跟物理收发器相关的部分解耦了。对比一下商用IP核的常规做法拿Xilinx的100G Ethernet IP核来说它通常把MAC和PCS/PMA绑在一起提供的是一个完整的数据通路。商用核的优势是经过充分验证、时序好、有GUI配置界面但有两个让人头疼的问题一是License费用高二是黑盒化严重内部出问题很难排查。我这几年在工程里遇到过几次商用IP核在特定条件下反压处理行为不符合预期的情况因为拿不到内部逻辑调试起来非常痛苦。verilog-ethernet虽然需要自己多做一些适配工作但所有代码都摊在面前出问题可以直接用ILA抓内部信号这种掌控感是黑盒IP给不了的。2.2 从25G到100G开源代码需要的改造点如果你之前用的是verilog-ethernet的10G/25G版本直接切到100G会碰到几个明显差异。第一个是数据通路位宽。25G时MAC接口位宽通常是64bit时钟跑到390.625MHz左右取决于实现方式100G CMAC接口通常是512bit或者4×128bit通道时钟大概是322.265MHz。这意味着你的UDP打包、IP打包模块处理的数据粒度完全变了。原来一个周期处理8字节现在一个周期可能要处理64字节状态机的设计思路要从字节流变成分片流。第二个是反压机制。100G下数据速率太高一旦下游不ready几个周期就能积压大量数据所以回压信号的处理必须格外小心。verilog-ethernet的AXI4-Stream接口本身支持ready/valid握手但多个模块串联时要做到整条链路无死锁、无丢包需要对每个模块的buffering深度做合理配置。尤其是用户逻辑读到DDR4的带宽如果跟不上100G线速反压会通过AXI4-Stream一级一级往上传递最后通过MAC的流控机制通知对端。这个链路如果某个环节处理不好轻则吞吐上不去重则直接死锁。第三个是时钟结构。100G CMAC通常有一个独立的gt_rx_clk和gt_tx_clk频率都在322MHz左右。但用户的DDR4控制器、业务逻辑往往是另一个时钟域。每个模块之间都要考虑CDCClock Domain Crossing处理。verilog-ethernet的很多模块是纯组合逻辑加寄存器天然适合做同步设计但当多个时钟域交汇时你需要自己插异步FIFO。这块最容易出隐蔽bug后面踩坑章节会专门讲。3. 硬件环境与系统架构VCU118上的整体数据流设计3.1 板卡选择和接口规划这次移植用了两块不同的板卡做过验证Xilinx VCU118和AU200。虽然都是UltraScale家族但细节差异影响很大。VCU118用的是VU9P带一个QSFP-DD接口可以拆成2×100G或者1×200G实际200G模式要两个通道绑定。AU200是Alveo加速卡有2个QSFP28接口每个支持100G。Alveo卡的好处是Power和散热更省心缺点是用户逻辑要和Shell里的DDR、PCIe做交互自由度略低。VCU118则全部资源都是你的适合做协议栈验证所以我最终选择在VCU118上做完整移植AU200跑了一个简化版。板卡上跟100G UDP直接相关的资源包括QSFP-DD光口接100G光模块这里用了一个100G SR4光模块通过MTP光纤连接对端的测试仪器Virtex UltraScale VU9P的GTY高速收发器100G用4路25.78125GbpsPAM4模式还是NRZ取决于光模块这里用的是NRZ的100G SR4板载DDR4 RDIMM速率2400MT/s带宽约19.2GB/s理论上够100G单向的线速缓存但实测发现读改写场景要留足余量板载UART和JTAG调试用3.2 整体数据流架构我把整个系统分成四层物理层GTY收发器绑定到CMAC硬核跑100G Ethernet协议。CMAC出来的接口是512bit AXI4-Stream带有rx_axis_tkeep、rx_axis_tlast等标准信号。链路层verilog-ethernet的MAC适配逻辑。CMAC出来的数据已经是以太网帧格式带前导码和FCS但也有可能带FCS错误这里用eth_mac_100g模块做了基本过滤。网络层与传输层eth_ip_udp模块完成ARP、IP、UDP的解析和封装。verilog-ethernet把IP和UDP做在一个模块里提供input_axi和output_axi两个接口中间通过一个小的lookup table配置本地IP和MAC。应用层自己写的DDR4缓存和DMA引擎。这部分最花时间因为要同时处理发送侧读内存打包和接收侧解包写内存两个方向的流水线。一个细节值得说发送方向和接收方向要分开设计FIFO深度。接收方向CMAC进来一个帧是连续的如果不及时搬走CMAC的FIFO会溢出丢帧。发送方向本地DDR4读取速率不一定刚好匹配MAC发送速率需要足够深的缓存平滑抖动。我用UltraScale的Block RAM搭了两个独立的异步FIFO发送方向深度设了16K×512bit接收方向深度设了8K×512bit。这个配置在大部分场景够用但如果对端突发流量特别大可以再加深接收FIFO。3.3 关键IP配置和约束要点在Vivado里例化CMAC时有几个配置项要特别注意Clock ConfigurationGT参考时钟用156.25MHz内部用户时钟会自动生成322.265625MHz。这里一定要检查CMAC输出的tx/rx user clock是否连对了很多人加了约束但忘了检查时钟树结果上板后数据全乱。Interface Configuration选择512bit AXI4-Stream接口开启TKeep、TLast。流控模式我建议选择Internal让CMAC自己处理pause帧这样开源协议栈这边就不用纠结flow control细节。RS-FEC100G SR4这种短距离光模块如果链路质量好可以不开启RS-FEC544,514能降低延迟。但如果你用铜缆或者长距离光模块建议开启。FEC开启后CMAC的延迟会增加约100ns左右对UDP这种无连接协议基本无感但对延迟敏感的应用要考虑。Preamble and FCS handlingCMAC默认收发都带前导码和FCSverilog-ethernet的MAC模块默认也是处理的。但有些开源代码示例里会在eth_mac_100g里做CRC校验和CMAC的FCS剥离逻辑重复了。这里要理清楚CMAC收到帧后会把FCS字段保留在以太网帧里verilog-ethernet的MAC模块会再次校验并剥离。我选择在MAC模块里剥离FCS然后IP/UDP解析模块就不再处理校验了避免重复计算浪费资源。时序约束方面100G设计对时序要求很严。Vivado里必须把CMAC输出的clk声明为create_generated_clock同时对所有跨时钟域的FIFO输入输出做set_clock_groups -asynchronous。不然综合后时序报告会一片红。我第一版没做时钟分组结果CMAC的rx_axis_clk到user逻辑的路径时序违约严重花了半天才定位问题。4. 移植过程中的核心改动从开源代码到能上板的工程4.1 模块裁剪和接口重映射verilog-ethernet原生的示例工程是面向某个特定开发板的直接拿来用肯定不行。我做的第一件事是把rtl/目录下所有模块按需拉出来eth_mac_100g、eth_axis_rx、eth_axis_tx、eth_arp、eth_ip_udp再加上ssioStream Sync IO处理位宽转换和axis_fifo。原工程里eth_mac_100g内部会例化Xilinx的Ethernet IP核但在我们的设计里CMAC已经在另一个层级例化好了所以我直接把eth_mac_100g内部跟CMAC有关的部分拿掉只保留对外接口对接逻辑。具体做法是创建了自己的cmac_wrapper模块把Xilinx CMAC的512bit AXI接口转成verilog-ethernet内部统一的mii_100g格式。这一步是整个移植里最核心的适配工作。mii_100g格式其实不复杂每个方向都有data[511:0]、valid、last和keep[63:0]信号接收方向多一个user信号携带错误标记。CMAC的AXI4-Stream格式和这个很像但多了tuser——它用来指示帧的错误类型比如CRC错误、帧长度错误。对接时要把tuser的几个bit映射到user信号上。这里有个坑CMAC的tuser在帧中间和帧结束时的含义不同不能简单当作普通数据信号处理。我后来写了一个小状态机去解析tuser确保只在帧结束位置提取错误标志。如果不做这个处理CRC错误帧可能被当成正常帧送到上层导致接收侧乱码。4.2 UDP发送路径的实现细节发送方向的数据流是DDR4读出数据 → 拼接UDP头 → 拼接IP头 → 拼接MAC头 → 送进eth_mac_100g→ CMAC发送。verilog-ethernet的eth_axis_tx模块帮我们做了以太网帧的封装但UDP和IP头需要自己填。它的input_axis接口接收的是已经包含UDP的IP数据报不含以太网头模块会自动加IP头和MAC头。所以用户逻辑的任务就是构造好UDP数据报UDP源端口、目的端口、长度、校验和然后交给eth_axis_tx。UDP校验和是个细节。IPv4下UDP校验和是可选字段全零表示不校验。对100G高速传输我建议直接写零关掉校验省掉每字节的累加逻辑——因为如果上层应用需要可靠性它应该走TCP或者自己的重传机制UDP本身不负责这个。如果是IPv6环境UDP校验和是强制的那就必须在发送侧做checksum计算。verilog-ethernet提供了eth_udp_checksum_gen模块做增量式计算可以每拍处理64bit100G线速下也能跟上。不过这个模块会引入流水线延迟我在UDP发送侧没开只在高可靠性场景下用过。报文拼接时要注意字节序问题。以太网是大端序但AXI4-Stream的数据位宽是512bit64字节每个字节在数据总线上的位置要跟tkeep对应好。比如一个UDP报文总长是65字节那么数据总线上就是第0拍64字节tkeep全1第1拍只剩1字节tkeep[0]1其余为0。tlast在第1拍拉高。很多新手在这里会搞错——把第二个有效字节放到tkeep[0]以外的位置结果抓包看到的数据全乱。发送侧另一个要命的点是帧间隙IFG。100G以太网标准要求帧与帧之间至少12字节的间隔CMAC会自动插入IFG你不用管。但如果你自己用状态机对接MAC层必须意识到IFG的存在否则MAC层的FIFO可能堆积。4.3 UDP接收路径的实现细节接收方向更复杂一些。CMAC收到帧后eth_axis_rx负责解析以太网头判断帧类型IPv4/ARP然后把IPv4帧送给eth_ip_udp模块。eth_ip_udp内部会先验证IP头版本、协议号、目的IP是否匹配本机再解析UDP头从output_axis输出用户数据。这里有个必须做的配置eth_ip_udp里面有一个过滤表要提前写入本机MAC地址和IP地址。如果不配置模块默认会接受所有目的MAC/IP的报文这在调试时看着方便但实际部署时会造成严重的安全问题——所有的广播包和数据包都会涌进你的用户逻辑。接收方向的DDR4写入逻辑我用了最简单的2MB环形缓冲区每次收到一个完整UDP数据报后在描述符里记录长度和时间戳。应用层软件通过PCIe访问定期轮询描述符把数据搬走。这个方案虽然简单但稳定可靠作为验证平台够用。接收方向最容易出的问题是帧碎片处理。CMAC送出来的帧不一定在一个beat内完成——大帧会分成多个512bit周期。我的接收状态机必须正确识别tlast才能判断一个UDP数据报是否完整。如果某个帧在传输过程中因为FIFO溢出被截断tuser标记错误接收状态机要能丢弃不完整帧并重新同步。这个逻辑如果不健壮很容易出现收到一半就写DDR的bug导致内存里留下垃圾数据。5. 上板测试的完整方法论ARL到UDP打流的分层验证5.1 第一层测试CMAC环回先排除物理层问题任何100G设计上板后第一步不是测UDP而是测物理层。我用Vivado的IBERTIntegrated Bit Error Ratio TesterIP先做了GTY眼图和误码率测试。这个步骤极其重要——100G链路的信号完整性要求远高于25G光模块接触不良、光纤弯曲半径过小、或者PCB走线问题都会导致大量误码。IBERT测下来如果BER好于1e-15物理层基本没问题如果误码率在1e-9到1e-12之间很可能光模块或链路有隐患建议先排查再继续。物理层测完做CMAC的本地环回测试。CMAC支持三种环回near-end PMA loopback在GTY内部环回、far-end PMA loopback在光模块端环回需要配合远端设备、以及MAC层的外部环回。我一般先做near-end PMA环回发已知pattern看能不能收回来。这个测试能确认CMAC的TX/RX数据通路没问题同时验证时钟是否正确。有两次遇到这种情况IBERT一切正常但CMAC环回时数据全错。最后查出来是CMAC的user clock没有接对——CMAC输出的tx_user_clk和rx_user_clk虽然是同频的但来自不同的PLL你不能想当然地在两个方向共用一个时钟。把两个时钟分别接进各自的FIFO问题立刻消失。5.2 第二层测试ARP先通ICMP再通物理层和MAC层确认无误后开始在verilog-ethernet协议栈上跑ARP。为什么先测ARP因为ARP测试能一次性验证整个以太网帧的处理链路CMAC收到广播帧 → MAC模块判断帧类型 → ARP模块解析请求 → 构造ARP回复 → 发送出去。ARP通了说明协议的接收和发送主链路是通的剩下的UDP只是在这个基础上换头部解析逻辑。当时通过Wireshark在电脑上抓包确认板卡能正确回复ARP请求后立刻转测ICMP。ICMP测试的意义在于它要求IP模块能正确处理TTL、校验和、以及ICMP echo request的构造和解析。用电脑ping板卡的IP能ping通说明IP层OK。这里遇到一个有趣的问题ping包能通但偶尔丢一两个包。抓包能看到板卡没有回复特定的echo request。后来用ILA抓到原因eth_ip_udp模块内部有一个小的状态机在解析IP头它默认只处理单个packet当连续两个echo request在很短时间内到达时第二个请求会在FIFO里排队而模块的配置表更新逻辑还没就绪导致第二个包被丢弃。解决方法是提高内部FIFO的处理能力同时增加一个忽略短时间重复请求的逻辑。5.3 第三层测试UDP回环与双向打流链路层和网络层都通了就是上真正的UDP应用。我先做了最简单的回环测试echo server板卡收到一个UDP报文解析出数据再打包发回给源地址。这一步跑通说明UDP收发是全通的。回环没问题后就进入最关键的双向打流测试。这里我用两个工具一个是iperf3它支持UDP模式可以指定带宽和报文大小做打流测试另一个是Scapy或者自定义的Python脚本用来构造特定payload大小、特定源端口/目的端口的UDP包方便定位问题。需要注意一点100G测试对测试仪器的要求比协议栈本身还高。电脑的普通千兆网卡根本承担不了100G线速打流你需要专业的网络测试仪比如Spirent TestCenter或者IXIA或者另一台配有100G网卡的服务器。我这边用了一台配了Mellanox ConnectX-6 Dx 100G网卡的服务器做对端跑DPDK自研的打流工具才能压到接近线速。打流测试中我发现了一个很有意思的现象小包64字节线速收包时接收侧DDR写入带宽不是瓶颈但处理小包的描述符开销成了瓶颈。当时用64字节包打满100G线速约1.48亿pps接收侧每个包都要写一条描述符导致DDR写带宽被描述符挤占UDP吞吐只有80G左右。改用DDR4的BURST模式批量写描述符后吞吐才回到95G以上。5.4 大包场景与带宽计算的预期管理很多人在100G项目上最关心的就是能不能打满100G线速。答案是取决于包大小。以太网开销是固定的前导码8字节 帧间距12字节。加上以太网头14字节、IP头20字节、UDP头8字节。所以一个UDP payload为1472字节最大MTU下的UDP数据的帧在线路上实际占了1538字节。理论最大帧速率 100Gbps / (8 × 1538) ≈ 8.13Mpps对应有效吞吐约95.7Gbps只算UDP payload则约94.5Gbps。如果是64字节的小包以太网帧总长84字节含IFG理论帧速率高达1.48亿pps但由于帧间距和前导码的占比有效UDP吞吐只有约50Gbps左右。所以打流测试时我一般分三档测64字节小包考验包处理能力、512字节中包、1472字节大包考验线速带宽。只有大包场景能看到接近100G的有效吞吐这是正常的不要以为协议栈有bug。6. 移植中踩过的坑以及对应的解决办法6.1 跨时钟域丢包处理CMAC与用户逻辑的时钟边界之前提到CMAC的tx_user_clk和rx_user_clk是独立的它们与DDR4控制器时钟也不同频。我第一版设计里UDP解析模块直接用了CMAC的rx_user_clk用户逻辑的DDR写模块用的是DDR控制器时钟中间靠一个简单的同步FIFO连接。理论上没问题但实际打流时出现了极其隐蔽的丢包——丢包率不到万分之一抓包看事件时间戳发现丢包间隔没有规律。排查过程很折磨人。用ILA同时抓FIFO的写侧和读侧信号发现偶尔会有一次写侧valid拉高但fifo_full同时拉高的情况——数据根本没写进去但上游模块认为已经写入。为什么会出现这种竞态后来看代码才发现同步FIFO的full信号是从写时钟域同步到读时钟域的用了两级寄存器同步这个同步过程会导致full信号在几个周期后才反映真实状态。当上游模块在full信号尚未更新但FIFO实际已满的那一瞬间拉高valid数据就丢了。解决办法是改用了Xilinx的xpm_fifo_async它内部把full信号的处理做了严格保证并且提供了almost_full信号用于预警。我把所有跨时钟域FIFO都换成xpm_fifo_async后丢包现象彻底消失。6.2 位宽转换的隐藏陷阱从64位到512位verilog-ethernet的很多模块内部是按64bit数据通路设计的它们是为10G/25G优化的。移植到100G后如果直接把512bit数据送进去模块内部的位宽转换逻辑会变成瓶颈。我踩过一个坑把CMAC出来的512bit数据直接接进eth_axis_rx模块虽然能工作但内部会自动拆分256bit、128bit、64bit多拍处理时序收敛变得极差Fmax从322MHz掉到200MHz左右。解决方案是在CMAC和解析模块之间插了一个位宽转换FIFO把512bit转成256bit。对verilog-ethernet的模块来说256bit是一个比较舒适的位宽——时序压力小状态机也好写。代价是数据通路变宽后用户逻辑处理每个beat的粒度变了所有FIFO深度要按bit数而不是字数量来重新评估。6.3 时序收敛100G设计绕不开的关卡100G的时序收敛比25G难一个数量级。322MHz的时钟频率下逻辑级数稍微多一点就会violation。我的经验是几条硬性规则数据通路尽量保持纯打拍不要在一个always块里做太多组合逻辑。校验和计算这种累计逻辑一定要用流水线分成多级寄存器而不是一个周期算完。大位宽数据总线的扇出很大布线上会脏建议关键的512bit数据总线加(* KEEP TRUE *)属性保留寄存器防止综合器过度优化。时钟约束一定写全CMAC的gt_rx_clk、gt_tx_clk、rx_user_clk、tx_user_clk都要声明跨时钟域路径用set_clock_groups -asynchronous隔离。如果不隔离Vivado会认为这些时钟是同源的去做跨时钟域时序分析导致大量假violation严重影响布线质量。6.4 回环测试的假现象Local Loopback的误导这是个非常坑的细节。我在做CMAC本地环回测试时用了CMAC自带的Local Loopback功能在GTY内部把TX信号环回到RX路径。所有测试都通过了——MAC链路通了、协议栈没问题、UDP回环正常。但是把环回关掉接上真实光模块后接收方向完全不通。排查了很久最后发现原因Local Loopback绕过了光模块和部分物理编码电路它只验证了GTY到CMAC这一段逻辑没有验证PMA的编码解码和光模块的电气层。我后来养成了一个习惯环回测试必须分三种做——Local LoopbackGTY内部、Remote/Line Loopback光模块或者对端设备回环、以及真实双向流量。只有最后一种通过了才算真正的链路通。7. 从Vivado工程到上板验证需要准备的材料清单和流程建议如果你打算自己复现整个移植过程建议按这个流程准备阶段一环境准备半天安装Vivado 2022.2或更新版本100G相关IP要求较新版本准备VCU118或类似带100G接口的开发板准备100G光模块和配套光纤SR4模块需要一个MTP/MPO接口的光纤跳线从GitHub拉取verilog-ethernet代码阶段二基础工程搭建1-2天创建Vivado工程例化CMAC IP配置好时钟和复位用IBERT确认物理层正常接入verilog-ethernet的MAC适配逻辑跑ARP和ICMP测试阶段三UDP通路开发2-3天接入eth_ip_udp模块配置本机IP/MAC编写发送侧和接收侧的DDR4读写逻辑实现UDP回环测试跑iperf3和自定义打流工具测试阶段四优化和稳定性测试1-2天检查小包、大包不同场景下的吞吐长时间打流稳定性测试至少24小时排查潜在的反压问题这里要提醒一下100G项目出问题时的排查速度很大程度上取决于你调试信号的可见性。我建议在关键节点预留ILA探针不要等到出了bug再重新综合加ILA——每次综合都要两三个小时。第一版设计时就把CMAC的AXI接口、FIFO的状态信号、UDP解析模块的关键状态机信号都接了ILA深度设成65536但注意ILA资源占用会影响放置布线几个就够了。8. 性能测试结果与优化方向最后分享一组实测数据供参考VCU118 100G SR4光模块对端为Mellanox ConnectX-6 Dx包大小理论线速率上限实测吞吐说明64B~50Gbps48.5Gbps受包处理速率限制出现描述符瓶颈512B~89Gbps87.2Gbps基本正常1472B~95.7Gbps94.8Gbps接近线速64B场景的吞吐差距后来通过批量描述符提交优化到了52Gbps左右但还是没到理论值瓶颈变成了DDR4的随机写带宽。如果要进一步优化可以考虑用AXI4的多个通道并行写、或者把小包聚合后再写内存。发送方向的吞吐表现要好一些1472B包能达到95.5GbpsCPU占用和反压都没问题。这里的经验是发送侧务必让DDR4的读数据提前预取到FIFO不要让MAC等数据。我用了一个简单的水位线触发机制FIFO数据量低于某一阈值时DDR读引擎就发起下一次突发读而不是等FIFO空再读。另外一个优化方向值得提verilog-ethernet supports aVLAN tag可选项如果你想在100G链路上跑QinQ或者VLAN隔离可以打开ETH_VLAN_ENABLE参数。我这次没用到但代码里有预留二次开发时不用改协议栈主体。从协议栈到完整可用方案其实还有几块硬骨头要啃PCIe DMA如果走host接口、多队列调度如果要支撑多业务、以及流表匹配如果要实现过滤功能。不过这些都是后续扩展的事先把100G UDP这条主链跑通后面的每一步都可以基于这条路去叠加。回过头看这次移植的核心收获不在于跑通了100G而在于彻底理解了从25G到100G真正变化的不是速率数字而是整个系统设计对带宽、并行度、时钟域管理的重新思考方法。如果下一步要做200G甚至400G我相信方法论是相通的——先把物理层验证扎实再逐层往上推先让单个方向通再做双向并发优化。这套打法看起来很朴素但在高速接口项目里恰恰是最可靠也最省时间的路径。