做FPGA网络设备的人早晚会遇到一个问题:单网口不够用。我之前做视频采集传输项目时,一开始是单光口接入,后来客户要求同时支持一路采集、两路转发、一路管理,这就得把板卡上的四个SFP光口全用起来。Kintex7 + AXI 1G/2.5G Ethernet Subsystem 是Xilinx生态里最成熟的组合之一,但四个网口同时工作,怎么配时钟、怎么分地址、怎么把数据通路串起来,这里面的坑比想象中多。
这篇文章是动手向的,以一块Kintex7四光口板卡为例子,手把手走一遍 AXI 1G/2.5G Ethernet Subsystem 主从级联的完整配置流程。你不需要对这个IP特别熟,但至少应该知道 AXI4-Lite 和 AXI4-Stream 的基本概念,知道"主""从"指的是发起方和响应方。我会把架构设计、IP参数、时钟复位、总线接线、约束写法、调试方法全部串一遍,过程中还会穿插我实际踩过的坑。做完之后,你能直接在类似板卡上复刻一套四口以太网方案。
1. 项目概述与整体设计思路
1.1 为什么选AXI 1G/2.5G Ethernet Subsystem:它解决什么问题
Xilinx 官方提供的以太网IP有好几类,老的Tri-Mode Ethernet MAC、后来的AXI Ethernet、以及2018.x以后主推的 AXI 1G/2.5G Ethernet Subsystem。这个新IP把MAC、PCS/PMA、GT收发器整合成了一个子系统,对外只暴露 AXI4-Lite 配置口和 AXI4-Stream 数据口,极大减少了MAC层和物理层之间的胶合逻辑。
它最方便的地方在于:当你用的是 SFP 光模块,且协议选 1000BASE-X/2500BASE-X 时,整个收发链路不需要外部PHY芯片,GT Bank 的收发器直接驱动光模块。这样板子上省了一堆 PHY 和 MDIO 网络,软件上也不用折腾 PHY 寄存器,只要把 MAC 和 DMA 初始化对,数据就能在 AXI 总线上跑起来。
四光口方案的实际需求很典型:要么做数据采集分发,要么做透明交换,要么做协议转换。无论哪种,本质都是"多个网口收发,数据统一进DDR,再由CPU或者DMA做决策处理"。所以这里选择这个IP是合理的,官方维护、文档齐全、回环测试方便。
1.2 "主从级联"到底级联什么:先把架构想明白
很多人一看到"主从级联"就以为要像菊花链一样把网口串起来,其实不是。在AXI语境里,"主"是发起读写的master,"从"是响应读写的slave。AXI 1G/2.5G Ethernet Subsystem 对外有两个维度:
- 配置维度:Ethernet Subsystem 的 AXI4-Lite 接口是 slave,它被 MicroBlaze 或者外部CPU配置。DMA 的寄存器接口也是 slave。
- 数据维度:DMA 是 AXI MM master,它发起对DDR的读写。Ethernet Subsystem 的 axis_tx 是 slave,接收DMA吐出来的数据;axis_rx 是 master,把收到的以太网帧递给DMA写进内存。
所以"主从级联"指的其实是:一个主控CPU通过 AXI 总线管理多个从设备(四个Ethernet Subsystem + 四个DMA),数据通路上DMA各自做master,DDR统一做slave。这个架构里最需要理清的就是"谁控制谁"和"数据往哪走",理清了之后整个设计就成功了一半。
我之前见过有新手把 axis_rx 直接接到另一个端口的 axis_tx,想模拟"级联转发",这种做法物理上能通但不具备实际意义,因为你没法做路由、过滤、速率匹配。正确做法永远是:先进DDR,再软件决策,再发出,也就是"存储转发"架构。
1.3 四光口数据通路规划:DMA怎么分工
四个网口对应四个独立的 Ethernet Subsystem 和四个独立的 AXI DMA。这里的"独立"非常重要,如果只带一个DMA然后用 AXI Stream Switch 去切换四个网口,虽然省LUT,但同一时刻只能一收一发,带宽直接被腰斩,而且调试时互相干扰,得不偿失。
具体分工如下:
- 每个 SFP 口对应一个 Ethernet Subsystem 实例(eth0~eth3),每个实例的 axis_tx/axis_rx 接各自的 DMA。
- 每个 DMA 通过 AXI SmartConnect 连接到同一个 DDR 控制器。
- MicroBlaze 作为主控,通过一个 AXI SmartConnect(或者 AXI Interconnect)把 AXI4-Lite 总线分发给所有 IP 的寄存器接口。
这样做的好处是:每个网口收发的数据路径完全独立,带宽互不影响;排查问题的时候可以单独停掉一路,ILA探针可以单独挂在其中一端口上,不会因为共享机制把问题搞混。
DDR带宽方面,四个1G口满速率收发的数据总量是 4 × 125MB/s = 500MB/s,即使加上MicroBlaze做管理平面的开销,对DDR3 1600(理论带宽约12.8GB/s)来说非常宽裕。如果在2.5G模式下四个口满跑,总量是1.25GB/s,仍然在DDR3的承受范围内,但AXI总线位宽和仲裁策略需要留意,后面第四节展开。
2. IP配置与时钟复位设计
2.1 创建IP:Vivado里的操作入口
打开Vivado工程,在IP Catalog里搜索 "AXI 1G/2.5G Ethernet Subsystem",直接双击添加。需要添加的IP实例包括:
- 4个 AXI 1G/2.5G Ethernet Subsystem
- 4个 AXI DMA
- MicroBlaze(作为主控,也可以用外部软核或硬核方案)
- 2个 AXI SmartConnect(一个走配置通路,一个走数据通路)
- 1个时钟管理(Clocking Wizard),负责把板载差分时钟转成多路用户时钟
IP配置界面看起来选项很多,但只要抓住核心那几项,其余保持默认即可。Vivado版本推荐2019.1以上,老版本这个IP还叫 AXI Ethernet,功能类似但寄存器布局有差异。
2.2 关键参数:速率、物理模式、GT通道
以1G光口为例,在IP配置界面里主要看这几项:
- Speed Selection:选 1Gbps 或 2.5Gbps。四个口可以各自独立选择,实际项目中建议统一,方便写驱动。
- Physical Interface:选 1000BASE-X(光模块直连,无外部PHY)。如果以后要接RJ45电口,才选 SGMII。
- GT Reference Clock:这里选 125MHz,这是GT收发器必须的参考时钟频率。不管你跑1G还是2.5G线速率,参考时钟都用125MHz,内部PLL会自动倍频。
- Management Interface:选 AXI4-Lite。这个接口用来配置MAC和状态寄存器。
- 启用内部回环:有一个 Loopback 的配置位,第一次上板建议先开 PMA loopback 做自检,后面调对端通信再关掉。
GT通道的选择要严格对照板卡原理图。四光口的板子,每个SFP笼子通常连到FPGA的特定GTX通道上,你必须到原理图里找到 SFP0~SFP3 对应的 GTX 位置,然后在IP的 GT Selection 里填对。如果填错,要么布线报错,要么信号完全不通。
2.3 时钟树设计:别让GT参考时钟背锅
多网口最容易翻车的就是时钟。四光口方案里,时钟需求是这样的:
- 板级给FPGA一个 GT 参考时钟,通常是 125MHz 差分时钟,接到 FPGA 的 MGTREFCLK 引脚上。
- 四个 Ethernet Subsystem 的 GT 参考时钟,最好全部由这一个 125MHz 时钟经过 BUFG_GT 分给四个 IP。不要给每个IP单独引不同的时钟源,否则四个口的PCS/PMA频率基准不一致,对端交换机收到的每个端口频率漂移都不一样,很难排查。
- 配置通路时钟 dclk/s_axi_aclk:用MicroBlaze的系统时钟,一般100MHz,统一供所有Lite接口和DMA寄存器接口。
- 数据通路时钟 axis_clk:1G模式下是125MHz,2.5G模式下是156.25MHz(配合16bit TDATA)。DMA 的 mm2s/s2mm 接口时钟直接用DDR侧时钟,比如200MHz。
实际操作时,我习惯把所有时钟在 Block Design 里用 Clocking Wizard 一次生成,并把每个Ethernet Subsystem的 axis_clk 连接在对应DMA的 axis 时钟上,保证跨时钟域只发生在IP内部。这种"一个时钟域里不要混多路数据"的原则,能帮你在写约束和调试时少掉一半头发。
2.4 复位设计原则:四个子系统要能独立复位
如果说时钟是骨架,复位就是神经。Ethernet Subsystem 的复位分两级:GT复位(gt_rx_reset/gt_tx_reset)和MAC/数据通路复位(axi_reset)。GT复位必须在PCS/PMA之前完成,否则内部状态机起不来。
多端口设计里,我强烈建议给每个Ethernet Subsystem和DMA都单独拉一个复位信号,做成一个"分组复位控制系统"。可以是一个简单寄存器,用CPU写一个 bit map,bit0对应eth0的复位,bit1对应eth1的复位,以此类推。这样调试任意一个端口,不用把所有口都重新初始化。
我曾经遇到过一个问题:四个口同时复位,其中一个口永远起不来,单独复位它就好了。后来查出来是复位释放瞬间,GT参考时钟没有完全稳定,四个口同时抢同一个PLL的控制权,导致某一个口的初始化时序被破坏。改成独立分组复位之后,这个问题再没出现过。
3. 主从级联接线实操
3.1 AXI4-Lite管理总线接线与地址分配
打开 Block Design,先把 MicroBlaze 搭好,然后添加两个 AXI SmartConnect。第一个 SmartConnect 专门用来分发配置通路,MicroBlaze的 M_AXI_DC 口(或者 M_AXI_DP)接进去,master侧接8个slave接口:eth0_lite、eth1_lite、eth2_lite、eth3_lite,以及 dma0_reg、dma1_reg、dma2_reg、dma3_reg。
地址分配可以这样规划(以64K对齐为例):
| 外设 | 基地址 | 大小 | 用途 |
|---|---|---|---|
| eth0_lite | 0x44A00000 | 64K | 以太网0 MAC配置 |
| eth1_lite | 0x44A10000 | 64K | 以太网1 MAC配置 |
| eth2_lite | 0x44A20000 | 64K | 以太网2 MAC配置 |
| eth3_lite | 0x44A30000 | 64K | 以太网3 MAC配置 |
| dma0_reg | 0x44A40000 | 64K | DMA0 描述符控制 |
| dma1_reg | 0x44A50000 | 64K | DMA1 描述符控制 |
| dma2_reg | 0x44A60000 | 64K | DMA2 描述符控制 |
| dma3_reg | 0x44A70000 | 64K | DMA3 描述符控制 |
这个分配的好处是:每个设备独占一个64K地址窗口,驱动代码里可以用基地址+偏移的方式访问,所有寄存器操作统一为"写偏移地址"。Bad Address 异常发生时,也能根据地址快速判断是哪个外设没有配置好。
MicroBlaze本身也需要一段指令数据和DDR空间,建议从0x00000000分配BRAM做BootLoader,然后把应用放到DDR里(比如0x80000000之后)。DDR的地址由 MIG 控制器决定,我的做法是在地址编辑器里手动分配,不依赖Vivado自动布局,因为自动布局偶尔会把 lite 和 DMA 的地址交错,写驱动容易踩错。
3.2 AXIS数据通路与AXI DMA的连接
配置通路接好之后,数据通路就简单了,但最容易被忽视。以 eth0 为例:
- eth0 的 m_axis_rx(数据从MAC来)接到 dma0 的 S2MM 接口(DMA写DDR)。
- eth0 的 s_axis_tx(数据发往MAC)接到 dma0 的 MM2S 接口(DMA从DDR读)。
- dma0 的 M_AXI_SG 和 M_AXI_MM2S/M_AXI_S2MM 全部通过第二个 AXI SmartConnect 接到 DDR 的 S_AXI 接口上。
第二个 AXI SmartConnect 的master侧挂的是四个DMA的M_AXI,slave侧只挂一个DDR控制器。SmartConnect 的仲裁策略我建议在 Address Editor 里把每个 DMA 的带宽请求设置为高,并且打开 "Enable QoS" 给每个 DMA 的 AR/AW 通道设置一个优先级,避免多口同时突发时互相饿死。
还需要注意一个细节:Ethernet Subsystem 的 axis_tx/axis_rx 接口位宽默认是8字节(64bit),但实际能否正确传输取决于 TUSER 信号是否能跟随数据包。AXI Ethernet Subsystem 的 axis_tx_tuser 带有用户控制信号,默认 Keep 信号就够了。如果在接DMA时发现 Keep 或 Last 信号被截断,多半是在 Block Design 里没有把 TUSER/TKEEP 打勾连接,一定要确认所有 tuser/tkeep/tlast 信号都已经连上。
3.3 引脚约束与综合实现
Block Design连好之后,剩下就是引脚约束。四光口的基本引脚包括:
- GT 收发器差分对:sfp0_rxp/sfp0_rxn、sfp0_txp/sfp0_txn,依次到sfp3。
- GT 参考时钟:板级125MHz差分时钟引脚。
- SFP 控制引脚:包括 TX_DISABLE、LOS、MOD_DEF0、MOD_DEF1、MOD_DEF2 等(具体看原理图)。
XDC 里 GT 引脚约束模板如下(引脚号按实际板卡替换):
set_property PACKAGE_PIN AH10 [get_ports {sfp0_rxp}] set_property PACKAGE_PIN AH11 [get_ports {sfp0_rxn}] set_property PACKAGE_PIN AJ10 [get_ports {sfp0_txp}] set_property PACKAGE_PIN AJ11 [get_ports {sfp0_txn}] set_property IOSTANDARD LVDS [get_ports {sfp0_rxp}] set_property IOSTANDARD LVDS [get_ports {sfp0_rxn}] set_property IOSTANDARD LVDS [get_ports {sfp0_txp}] set_property IOSTANDARD LVDS [get_ports {sfp0_txn}]需要特别强调:GT 差分引脚不要用普通 IO 标准,必须用 LVDS 或类似差分标准。参考时钟的约束同理。SFP控制引脚是单端信号,一般用 LVCMOS18 或 LVCMOS25,具体看FPGA bank供电电压,不要把电压设错导致烧模块。
综合实现时,Vivado 会自动处理 GT 相关的时钟约束,通常不需要手动 create_clock。但多个IP共享同一个GT参考时钟时,最好在约束文件里显式声明参考时钟,避免综合工具把它当成异步时钟处理,产生出乎意料的时序路径。
3.4 软件驱动初始化顺序
IP硬件连接全部完成后,软件初始化顺序也很有讲究。我的经验是严格按这个顺序:
- 配置 MIG 和 DDR 控制器,等待 calibration done。
- 把四个 DMA 的复位和四个 Ethernett Subsystem 的复位全部释放。
- 等待每组中断或状态寄存器确认 MAC 核已退出复位。
- 逐个配置 MAC 的速率(1G/2.5G),设置 MAC 地址,打开 TX/RX 使能。
- 初始化 DMA 描述符队列,并把 DMA 的 S2MM 和 MM2S 通道 Select 好。
- 先开 DMA 的 S2MM(RX通路),再开 MM2S(TX通路),后开 MAC 的 TX/RX。
顺序不能乱,尤其不要一开始就打开 MAC 的 TX,否则DMA没有准备好,数据来了没地方放,直接造成 RX overrun 中断风暴。
4. 调试与问题排查实录
4.1 光模块链路起不来:先查GT参考时钟
这是四口方案里最经常出现的问题。现象是 ILA 里看 Ethernet Subsystem 的 status 寄存器 link 位一直为0,或者 PCS/PMA 报 loss-of-signal。
第一步永远是确认GT参考时钟有没有正常送到IP。在 Vivado 里打开 hardware manager,看 PRB 和收发的 init_done 信号,或者直接看 ILA 抓 gt_rx_status 的状态。如果 init_done 一直拉低,八成是参考时钟有问题。用 ILA 抓一下 MGTREFCLK 的 frequency_counter 寄存器,能直接读出参考时钟频率。
我遇到过最坑的一次:原理图里 SFP0 和 SFP1 的参考时钟走了一根共享的差分对,切换开关需要 FPGA 输出一个 GPIO 来控制 MUX。结果 GPIO 初始值默认选择了另一个时钟源,导致所有光口都没信号。排查到最后一层才在硬件手册小字里看到这个MUX。这事告诉我们:拿到新板子第一件事,把原理图里所有和 GT 时钟相关的 MUX/放大器/电平转换都过一遍,再谈配置。
另一个常见情况是:SFP模块的 TX_DISABLE 引脚没有拉低或没有控制。大部分SFP模块要求TX_DISABLE为低电平时才允许发光。如果这个引脚悬空或者默认上拉,模块就一直处于"静默"状态,对端当然收不到光。Vivado 约束里记得把 TX_DISABLE 这个信号引出并处理,不能图省事不接。
4.2 回环测试正常但对外不通
内部回环(PMA loopback)能通过说明 MAC、PCS、GT 收发链路本身没问题,数据路径是通的。但对外通信时,经常遇到"灯亮了但 ping 不通"的情况。这种问题,我用两条思路排查:
- 速率不匹配:对端交换机或者另一块板卡是 1G,而你这边配的是2.5G,光模块协商不出来,物理层就断了。先用 serial terminal 或 ILA 抓 status 寄存器里的速度字段确认当前速率。
- 编码方式不匹配:1000BASE-X 和 SGMII 的自动协商机制不同,如果对端用的是 SGMII 交换机,而你这边配的是 1000BASE-X,会一直 idle 但建立不了 link。需要改成 SGMII 模式,或者强制1000BASE-X。
还有一种情况是光模块本身只支持 1.25G 线速率,你却把它用到 2.5G 模式,模块直接不工作。选模块前看一眼规格书里的速率等级,不要默认光模块都能扛2.5G。
4.3 多网口同时跑:带宽和仲裁问题
四个口同时跑的时候,最容易出问题的是 AXI SmartConnect 的仲裁策略,尤其是在 2.5G 模式下。四个网口的总线吞吐量加起来是1.25GB/s,虽然DDR3带宽够用,但AXI总线是分时复用的,当一个DMA长期占用 burst 通道不放,其他DMA的 response 延迟就会显著增大,表现出来就是某一端口丢包率突增。
解决方案有两个层面。硬件上,在 SmartConnect 里把每个端口对应 DMA 的 QoS 优先级配成一样的权重,并打开 "Burst 转换" 让短突发合并成长突发,减少总线占用次数。软件上,把 DMA 描述符队列长度加长,比如默认64个描述符,改成256个或512个,给 DMA 更大的缓冲来吸收总线的瞬时抖动。
还有一个我在实测中确认过的技巧:让每个端口的 RX 描述符放在不同的 DDR bank 或不同的 memory region,减少多个 DMA 同时访问同一页面导致的 bank 冲突。在 Zynq 上还能用 OCM 来放 descriptor,但在 Kintex7 上只能靠 DDR 地址交错,效果也还不错。
4.4 复位时序与初始化僵局
与复位相关的僵局,典型表现是第一次复位能工作,但软件软重启之后某个端口起不来。查到最后基本都是复位释放时机的问题。
AXI 1G/2.5G Ethernet Subsystem 的完整初始化需要等待三个事件:GT 的 txresetdone/rxresetdone、PCS/PMA 的 align_status、以及 MAC 的 reset_done。这三个信号,任何一个没有拉高就急着开DMA,后续行为都是不可预期的。
所以我在驱动里加了个"三阶段确认"逻辑:第一步软件复位IP,第二步轮询 status 寄存器确认 GT 完成,第三步等待 rx_los 拉低、align_status 拉高,第四步才使能 MAC TX/RX。这个顺序在四端口下运行时尤其有效,因为四个GT通道都是独立初始化的,完成时间各不相同。
另外,复位信号本身最好用同步复位,且不要直接用板级复位按钮接一路给所有IP。用 MicroBlaze 的 GPIO 输出多个复位信号,并且每个复位信号都接入一个 UtilsFF 的同步器,能显著降低复位释放时亚稳态的概率。FPGA 里的复位同步问题,处理不好就是偶发性宿命,处理好了就是一次过。
最后一句话
这四个光口的配置流程,我前前后后调了两周。后来每次做新板子,我都先把时钟树画清楚、把地址映射表定下来、把"主从关系"写清楚,再开 Vivado。All of this,说起来是技术,做起来是耐心。如果你在做类似多网口方案,碰到什么问题,欢迎按文里的排查思路走一遍,大概率会比我那两周顺利得多。