
说一个很有意思的项目Zynq UltraScale上跑2.5G网络加速目标是用Xilinx官方的AXI Ethernet Subsystem把口子上的吞吐量打到2.5Gbps这条线上。先说清楚这不是标题党式的“跑满2.5Gbps有效载荷”而是要让MAC层/PHY层跑在2.5G速率等级实际UDP能到多少、TCP能到多少后半篇我会用带宽预算掰开算清楚。做这个事的场景很典型边缘网关、视频采集卡、工业控制盒、网络加速板卡上游交换机和NAS已经全面转向2.5G BASE-T端口但主控CPU的集成MAC还停在1G这时候在FPGA里补一个2.5G MAC就非常自然。这篇文章适合正在用Zynq UltraScale/Versal做高速接口、在PL侧做数据处理加速的工程师也适合刚把AXI Ethernet摸了一遍但始终觉得吞吐量不对劲的朋友。我把整个项目的配置过程、数据通路设计、时钟与DMA细节、以及最后调吞吐量的踩坑记录都整理在下面不是PPT式的方案宣讲全是上板调过的经验。1. 项目背景与方案选型为什么在Zynq UltraScale上做2.5G加速1.1 2.5G这个速率的特殊位置以太网速率序列里1G和10G之间一直有个明显的空档。10G网络成本和布线要求高普通设备用不起1G又越来越不够用特别是Wi-Fi 6无线AP的回传、NAS多用户并发、工业图像采集这几类场景2.5G正好踩在“成本可控、性能翻倍”的甜点上。2.5G BASE-T物理层可以沿用Cat5e网线交换机和PHY芯片价格这两年也降下来了所以新型工业交换机、高性能嵌入式主板上到处都是2.5G口。但在FPGA侧2.5G是一个有点尴尬的中间速率。Zynq UltraScale PS端的集成GEM只能跑到10M/100M/1G想上2.5G只能从PL侧想办法。PL里的GT高速收发器本身支持2.5Gbps毫无压力关键是怎么把一个合适的以太网MAC IP配出来并且让整个数据通路不浪费这个速率。另外一个容易搞混的点是2.5G SGMII是一种扩展速率模式和标准SGMII1G并不完全一样。标准SGMII自动协商机制只定义了10M/100M/1G2.5G是靠扩展能力协商或寄存器强制指定的。所以用AXI Ethernet Subsystem做2.5G时链路协商、速率配置、PHY模式这些环节都会出现1G时代没有的坑。1.2 方案对比AXI Ethernet Subsystem vs 其他选项给Zynq UltraScale加2.5G网络口实际可选的路有几条我评估过一遍PS端GEM 外部PHY最省事但PS GEM的最高速率就是1Gbps直接排除。PL端AXI Ethernet SubsystemXilinx官方软核MAC支持SGMII接口且部分版本支持2.5G速率模式驱动生态成熟Bare-metal和Linux都有现成驱动开发周期短。这是一个非常标准的做法。PL端10G/25G High-Speed Ethernet Subsystem资源开销大、配置繁琐40G/100G的MRMAC用在2.5G上纯属杀鸡用牛刀而且IP授权和时序收敛成本都高。纯RTL自研MAC自由度高但实现MAC控制、MDIO管理、错误处理、驱动对接这些工作量很大正常项目周期内不推荐。综合下来AXI Ethernet Subsystem是唯一一个兼顾官方支持、轻量级、驱动成熟、能跑2.5G的方案。它的定位就是“给嵌入式系统补一个灵活的以太网MAC”资源占用不高时序压力小比较适合和自定义加速逻辑放在同一个设计里。1.3 整体数据通路与带宽预算分析整个数据通路的顶层结构是外部2.5G PHY通过SGMII差分对接入Zynq UltraScale的GT引脚PL侧进入AXI Ethernet Subsystem完成MAC层处理FCS校验、帧过滤、统计MAC出来的AXI4-Stream数据流交给AXI DMADMA再把数据搬运到PS DDR内存中应用层裸机或Linux协议栈去DDR里直接访问数据。这个通路的带宽预算要从两个层面看线速层面2.5G SGMII线速为2.5Gbps8B/10B编码后有效数据速率是2.0Gbps。以太网最大帧1518字节加上前导码8字节和帧间隙12字节有效载荷占比约为1518/1538 98.7%所以MAC层最大理论吞吐约1.974Gbps。如果跑UDP扣除MAC头14字节、IP头20字节、UDP头8字节用户payload最大约1472字节对应的理论最大UDP吞吐约1.91Gbps。这一点建议提前跟需求方对齐否则验收的时候容易扯皮。内部总线层面AXI DMA到PS DDR的路径如果用128位AXI总线、150MHz频率原始带宽有2.4GB/s远高于2.5G链路需求但如果AXI总线配成64位、100MHz只有800MB/s虽然仍高于2.5Gbps链路速度312.5MB/s但加上DDR其他master竞争和描述符访问开销余量就非常紧张了。所以配置时尽量用128位数据路径和较高的fabric时钟。一句话总结方案选型的关键是“链路接口由PL承担数据搬运由DMA完成CPU只处理协议栈”这是FPGA网络加速的经典架构后续所有配置和调优都围绕这个架构展开。2. AXI Ethernet Subsystem配置要点逐项拆解2.1 IP级配置接口、速率与缓冲区在Vivado中添加AXI Ethernet Subsystem后第一个要仔细核对的就是接口类型。做2.5G时选择SGMII接口同时要确认IP版本和配置选项里支持2.5G速率。我用的Vivado版本较新可以在IP配置界面看到”SGMII 2.5G”相关的使能选项。如果你的版本里找不到最好检查一下IP版本或升级Vivado老版本里这个选项可能根本不出现。接口确定后几个关键配置项Data Interface选择AXI4-Stream这样MAC数据流可以直接跟DMA对接不适合再接自定义加速逻辑时使用AXI4-Stream也是最灵活的。DMA Type这里有两种路径一种是让AXI Ethernet内部集成的DMA直接工作另一种是配置成不带内部DMA只输出AXI4-Stream外接一个独立的AXI DMA IP。我更推荐后者原因有两个独立AXI DMA的Scatter-Gather和描述符管理更直观调参灵活后续如果想在两个MAC之间做交换或插入自定义处理逻辑数据流在stream层面操作比扒开内部DMA容易得多。FIFO/缓冲深度收发FIFO深度尽量选大一些建议16KB或以上。我之前遇到过FIFO深度配置过小导致背压频繁、瞬时突发流量直接掉包的情况。对2.5G来说16KB FIFO配合DMA基本够用32KB会更稳代价是多占一点BRAM/URAM资源。Flow Control关掉802.3x流量控制或者至少在调试阶段关掉。MAC发出Pause帧后对端交换机会自动限速如果接收链路某个环节处理不过来Pause机制会反过来压住发送吞吐量排查起来非常隐蔽。MDIO接口默认是要打开的外部PHY的管理就靠它。有些设计犯懒不接MDIOPHY的速率协商全靠默认配置这样往往只能协商到1G2.5G压根跑不起来后面排查会非常被动。2.2 GT参考时钟与管脚约束SGMII在Zynq UltraScale里要经过GT高速收发器而GT的参考时钟是整个PHY层的心脏。2.5G SGMII的实际线速率为2.5Gbps在8B/10B编码下对应GT的TX/RX线速率为2.5G参考时钟通常配置为125MHz。很多新手在这块会犯嘀咕1G SGMII也是125MHz参考时钟2.5G怎么还是125MHz因为GT内部有PLL倍频同样的125MHz输入经过不同分频倍频组合就能得到2.5Gbps线速率。GT参考时钟有几个硬性约束需要提前确认时钟来源优先用专用差分参考时钟引脚不要从普通PL引脚分出来。参考时钟抖动直接影响误码率2.5G下稍微差一点轻则retry重传重则链路起不来。Quad位置GT参考时钟所在的Quad必须与SGMII引脚所在的Quad一致或者至少确保布线可达。跨Quad的时钟分配会增加额外的时钟路径延迟和不确定性2.5G这种速率下不推荐。PHY的时钟模式SGMII链路有个master/slave时钟方向问题PHY和MAC之间谁提供125MHz参考时钟要明确。一般外部PHY自带晶振或支持从MAC侧接收时钟配置时要看PHY手册里的clock source寄存器并在MDIO初始化阶段设置正确。这个方向接反了GT的reset done也会异常链路完全没有。在XDC约束里GT参考时钟引脚要单独约束并且加上create_clock指定频率。另外差分信号的P/N引脚不要搞反SGMII的TX/RX极性反了也能偶尔link上但数据全是CRC错误。2.3 AXI DMA与缓存一致性设计AXI DMA是整个数据通路的搬运工它的配置质量直接决定能不能跑到2.5G。我常用的配置组合是Enable Scatter Gather打开SG模式描述符由DMA自动管理收发可以离散到多个内存缓冲区适合高吞吐场景。Stream Data Width如果PL侧接口允许设成64位或128位减少握手周期数。Max Burst Size设为256一次突发传输能搬更多数据降低描述符获取频率和总线握手开销。Buffer Length Register Width选择23位单个缓冲区可以支持到8MB长度避免大包拆多次DMA传输。然后是一个特别容易被低估的坑缓存一致性。PL侧DMA写DDR的时候是绕过CPU L2 Cache的如果CPU之前恰好缓存了同一段地址的数据DMA写完新数据后CPU读到的是老缓存数据校验必然失败TCP重传率飙升吞吐量惨不忍睹。这个问题有两种标准解法硬件方案把AXI DMA的数据接口通过带Snoop功能的端口接到PS侧的SCU上DMA写DDR时自动做一致性维护软件方案在DMA完成中断回调里手动做Cache失效操作接收方向在读取数据前调用Xil_DCacheInvalidateRange发送方向在DMA启动前调用Xil_DCacheFlushRange。硬件方案最彻底但需要关注AXI接口是否支持ACE-Lite协议软件方案通用性强我实际项目也是以软件方案为主代价是多一点CPU开销但对2.5G这个级别完全可接受。DMA描述符本身也要注意Cache Line对齐通常要求64字节对齐描述符数组放到单独的内存段里不要和payload缓冲混在一起避免CPU与DMA同时访问时的伪共享问题。2.4 软件初始化与速率设置硬件配置完成后软件侧的顺序也重要。我的初始化序列大致是等待GT复位完成确认reset_done置位通过MDIO读外部PHY的ID和厂商寄存器确认PHY可达配置PHY进入SGMII 2.5G模式这一步可能涉及扩展寄存器或厂商自定义寄存器不能用标准1G自动协商的流程初始化AXI Ethernet MAC设置MAC地址调用速率设置函数把MAC工作速率设为2500Mbps初始化AXI DMA通道和描述符使能中断开始收发。在Bare-metal下速率设置相关的接口在xaxiethernet.h里有现成函数可以直接调用设定工作速率。Linux下则在设备树里配置phy-mode sgmii同时在PHY驱动里确认2.5G协商是打开还是强制必要时可以在驱动里把速率强制到2.5G。一个重要的经验是软件初始化顺序不能乱。如果先配置MAC速率再初始化PHY或者GT还没锁定就写PHY寄存器会出现速率寄存器配置了但实际链路仍停留在1G的情况。2.5G SGMII不是一个完全标准化的自动协商流程有时直接靠寄存器强制是最可靠的办法。3. 实操过程从Vivado工程到2.5G吞吐量验证3.1 Block Design搭建关键步骤我以Vivado 2021.2以上版本为例完整走一遍Block Design的搭建过程。芯片型号用的XCZU9EG但流程对其他Zynq UltraScale器件同样适用。第一步创建RTL工程后新建Block Design添加Zynq UltraScale MPSoC IP。在PS配置里打开需要的MIO、DDR、UART、GPIO和PL时钟输出我这里给PL侧提供两个fabric时钟一个150MHz作为AXI总线时钟一个125MHz作为SGMII的接口时钟如果PHY需要MAC侧提供参考时钟的话这个时钟要接到外部PHY的时钟引脚上。第二步添加AXI Ethernet Subsystem IP配置项按前面说的SGMII接口、2.5G使能、AXI4-Stream数据接口、关闭内部DMA、打开MDIO。FIFO深度选择16KB或32KB。第三步添加AXI DMA IP打开Scatter Gather设置Stream数据宽度为64位或128位Max Burst Size为256。把AXI Ethernet的s_axi、axis_rxd、axis_txd分别连到AXI DMA的MM2S/S2MM通道和AXI-Lite控制接口上。第四步时钟和复位连接。AXI Ethernet和AXI DMA的AXI-Lite接口时钟、Stream接口时钟统一用150MHzGT参考时钟由专用引脚输入在Block Design里需要用Clocking Wizard或直接用IBUFDS_GTE原语引入125MHz差分参考时钟。第五步中断连接。AXI DMA的mm2s_introut和s2mm_introut都接到PS的PL-PS中断端口上AXI Ethernet的中断也一并接过去。中断在系统里要能区分不同来源软件处理效率会高很多。第六步写管脚约束。GT参考时钟引脚、SGMII的TXP/TXN/RXP/RXN引脚、MDC/MDIO引脚全部在XDC里指定并加上必要的create_clock约束。第七步综合实现生成bitstream导出硬件到Vitis/SDK。整个过程看起来不复杂但有几个位置容易翻车GT refclk的输入buffer选型、跨时钟域的复位连接、以及AXI Ethernet IP内部时钟和外部fabric时钟是否满足速率等级约束。如果综合后出现时序违例优先检查GT refclk和fabric时钟的来源是否干净。3.2 驱动与测试代码骨架裸机测试代码建议直接从Xilinx的示例工程改比从头写省事得多。初始化部分的大致骨架如下#include xaxiethernet.h #include xaxidma.h XAxiEthernet AxiEthernet; XAxiDma AxiDma; XAxiEthernet_Config *EthConfig; XAxiDma_Config *DmaConfig; static void eth_rx_callback(void *callback_ref) { // 在接收完成中断里做CacheInvalidate、解析数据、回收描述符 Xil_DCacheInvalidateRange((INTPTR)rx_buffer_ptr, RX_BUFFER_SIZE); // 统计吞吐量 } int main(void) { EthConfig XAxiEthernet_LookupConfig(ETH_BASEADDR); XAxiEthernet_CfgInitialize(AxiEthernet, EthConfig, EthConfig-BaseAddress); DmaConfig XAxiDma_LookupConfig(DMA_BASEADDR); XAxiDma_CfgInitialize(AxiDma, DmaConfig); // 初始化PHY通过MDIO写入2.5G模式寄存器 init_phy_2g5(); // 设置MAC地址和速率 u8 mac_addr[6] {0x00, 0x0A, 0x35, 0x01, 0x02, 0x03}; XAxiEthernet_SetMacAddress(AxiEthernet, mac_addr); XAxiEthernet_SetOperatingSpeed(AxiEthernet, XAE_SPEED_2500_MBPS); // 启动DMA发送和接收通道同时使能 start_ethernet_dma(AxiDma); // 主循环打印统计 while (1) { print_speed_stats(); sleep(1); } }注意示例代码只是骨架实际工程还要处理描述符分配、环形缓冲管理、收发统计计数等。测试时建议先跑UDP回环和外部主机互发再上TCP协议栈。为什么建议先裸机因为Linux lwIP/网络协议栈本身就有一堆缓冲和内核调度开销裸机跑通了再套协议栈性能问题才分得清是协议栈的还是底层通路的。3.3 吞吐量实测结果与调优曲线我用一个支持2.5G SGMII模式的PHY芯片对接测试。硬件链路里还加了一个2.5G交换机主机用双口网卡其中一口2.5G连交换机另一口1G用作管理。裸机下写一个UDP发送程序按1472字节payload发送测试结果如下初始状态收发能通但速率只有1.2Gbps左右远低于2.5G链路能力。用ILA挂在AXI Stream接口上看Valid拉起的时间占比大约60%说明数据通路中间有大量等待周期。第一次调整把AXI DMA的Max Burst Size从64调到256FIFO深度从8KB调到16KB速率提到1.55Gbps。总线突发传输效率提升明显。第二次调整接收方向每次DMA中断都调用Xil_DCacheInvalidateRange发送方向加Xil_DCacheFlushRange速率到1.78Gbps。这证明之前有一部分重传或校验失败浪费了带宽。第三次调整关闭MAC的802.3x flow control关闭调试串口打印速率上升到1.88Gbps。这个值已经非常接近前面算的UDP理论极限1.91Gbps了。最后再测一轮TCP直接SSH传大文件实际稳定在1.6Gbps左右。TCP掉一些很正常协议确认、窗口管理和CPU协议栈处理都有开销。如果是产品化项目想把TCP也压到接近2Gbps就要考虑把收发做成中断合并、使用更大的描述符池或者在Linux下用NAPI和RPS/RFS做多队列优化。4. 常见问题与排查技巧实录4.1 链路协商不上2.5G的排查路线这是整个项目里最常见的拦路虎。现象是PHY link灯亮了但协商速率只有1Gbps怎么改MAC侧都不行。我遇到过一次DATACOM的PHY型号上电默认在1G自动协商模式SGMII那边的扩展2.5G能力位根本没被打开。排查步骤建议按顺序来用MDIO回读PHY的控制寄存器和状态寄存器确认PHY当前实际协商到的速率。如果寄存器显示1G说明问题在PHY侧的协商配置MAC这边再怎么设置也白搭。查PHY手册里SGMII速率控制相关的寄存器找有没有“SGMII rate select”或“extended speed mode”之类的字段把它设置为2.5G同时把自动协商关闭或改为2.5G扩展协商。确认GT链路本身是好的看GT reset done寄存器是否置位用IBERT或内部loopback测试GT收发电通路排除硬件链路问题。确认SGMII时钟方向PHY是master还是slave有些PHY需要MAC侧输出125MHz参考时钟有些PHY自己提供配置反了以后链路完全无法建立。我把问题现象整理成一个速查表现象可能原因排查手段Link速率停留在1GPHY未进入2.5G模式MDIO读PHY速率寄存器强制2.5GLink灯不亮GT refclk异常或极性反检查GT reset doneIBERT自环Link正常但CRC错误爆表SGMII差分极性错误或抖动过大交换差分P/N极性尝试检查参考时钟质量Link在2.5G但速率不稳PHY与MAC时钟方向冲突确认master/slave时钟模式只有发送方向通GT接收链路故障或PHY信号检测异常GT RX的reset和loopback项检查4.2 吞吐量上不去的系统级归因链路协商到2.5G只是第一步吞吐量上不去的坑比链路协商更隐蔽。我遇到或帮别人排查过的原因大概有这几类第一类是DMA配置问题。描述符池太小、burst长度太短、Stream数据位宽不够都会让DMA频繁发起传输请求总线被握手开销占满。这类问题在ILA上看AXI Stream的Valid/Ready比例最直观如果握手周期占比很高但吞吐量还是低大概率是DMA描述符管理开销。第二类是缓存一致性问题。CPU读DMA写入的数据时拿到旧缓存导致上层协议校验失败重传。这个坑最坑的地方在于用大包测试时可能不明显用TCP小包测试时吞吐量会掉到低得离谱。查这个问题有个技巧在接收中断里临时关闭CacheInvalidate如果数据错误率显著上升就说明一致性路径有隐患。第三类是CPU中断风暴。2.5G链路满速时中断频率非常高如果每个包都触发一次中断CPU大部分时间都耗在上下文切换上。解决方法是开中断合并coalescing、增加单次中断处理的包数量或者直接用轮询模式NAPI读取描述符。第四类是DDR带宽竞争。Zynq UltraScale里DDR带宽被CPU、PL DMA和其他的master共享如果视频采集或其他模块也在大量占用DDR以太网DMA可能经常等总线。可以在PS配置里调整AXI端口的QoS优先级给以太网DMA更高的带宽权重。第五类是描述符和payload缓冲没有正确对齐。Xilinx DMA要求描述符64字节对齐payload最好也按Cache Line对齐。不对齐的情况下Cache维护操作效率会大幅下降有时还会触发多余的数据搬移吞吐量损失严重。4.3 避坑经验与调试工具建议基于这个项目我把几个调试工具和手法的经验分享一下。调试顺序很重要我后来养成习惯了凡是跑高速以太网一定是分层验收GT层先用IBERT对GT收发通道做扫频和误码率测试确认物理层没问题。如果IBERT都不过后面应用层的检查全是白费。MAC层在AXI Ethernet内部把TX回环到RX让MAC自发自收验证MAC IP本身没有配置问题。PHY层用外部PHY的loopback寄存器做远端回环测试SGMII到PHY这一段。DMA层写一个简单的DMA自发自收例程确认DMA中断、描述符和数据内容都是正常的。协议层最后才上UDP/TCP协议栈调吞吐量。工具方面Vivado自带的ILA是我最常用的。把AXI Stream接口的tvalid/tready信号挂进ILA里看看Valid拉低的时候是在等数据还是等反压基本能判断瓶颈是在MAC、DMA还是在缓存一致性处理上。另外Xilinx的Integrated Logic Analyzer在调试PCS层眼图的时候也有大用。还有一个日常容易犯的错调试串口每隔几百毫秒打印一次统计信息看起来频率不高但在满速吞吐下UART打印占用CPU时间和中断处理时间会让吞吐量掉5%甚至更多。我在项目里就把吞吐量统计改成每5秒输出一次或者直接放到Linux用户态用proc文件查看效果立竿见影。调试工具清单IBERT验证GT物理层扫BER和眼图ILA抓AXI Stream握手信号和DMA寄存器MDIO read/write小程序排查PHY寄存器状态独立UDP压力测试工具对比主机到FPGA的收发速率LTTng/perfLinux下定位协议栈CPU热点。最后再分享一个我觉得特别实用的习惯先把裸机驱动调通再上Linux。Linux下网络栈层次太多出了性能问题很难定位是驱动问题、DMA问题还是协议栈问题。裸机下能控制每一个细节先确认MAC/DMA通路的极限速率再放到Linux下用同样的测试方法做对比哪个环节引入的损耗就一目了然。这套方法帮我节省了大量排错时间也让我在后来做10G/25G项目时依然心里有底。