☰
FPGA多光口网卡/交换机:AXI Ethernet Subsystem主从级联配置实战
2026/10/6 10:59:28 网站建设 项目流程

FPGA 多光口网卡/交换机这件事,圈子里的朋友应该都有感触:网上聊 AXI 1G/2.5G Ethernet Subsystem 的文章不少,但绝大多数是单光口、单 IP 的 demo,真正到了四光口、八光口这种规模,还要把每个口都跑起来、还要互通转发的时候,坑一下子就多起来了。尤其是主从级联(Master/Slave Cascade)这种玩法,好多资料都是一句话带过,说“把多个 IP 实例化就行了”,可实际接上去不是链接起不来,就是只有一个口能通、剩下几个口全部罢工。我在这上面前前后后折腾了两三周,把 Xilinx 官方文档从头翻到尾,又对着 Vivado 里的原理图逐条核对,才把整个级联链路理顺。

这篇文章就把我踩过的坑和最终跑通的配置方法完整写出来。内容围绕 Xilinx AXI 1G/2.5G Ethernet Subsystem(后面简称 AXI Ethernet IP)做多光口扩展展开,重点讲清楚主从级联的时钟架构、复位时序、IP 参数匹配、寄存器配置,以及最常见的链接不稳定、单口不通、CRC 错包等问题怎么查怎么解。适合正在做 FPGA 网卡、多口交换板卡、或者准备用 FPGA 替代商用交换芯片做二层转发验证的工程师参考。是有一些门槛,但只要你用过 AXI Ethernet IP 的单口版本,这篇文章应该能帮你少走一大半弯路。

1. 整体方案设计与主从级联的核心思路

1.1 为什么用 AXI Ethernet Subsystem 做多光口交换机

先说需求背景。很多时候我们做 FPGA 板卡,不光是做一块“能发包”的网卡,而是要做一个能充当小型交换机或者协议转换板的东西。商用交换芯片当然有,比如 Marvell、Broadcom 那一堆,但问题是交换芯片的内部转发表、VLAN 处理、流控逻辑都是封闭的,想在里面塞自定义的帧格式或者做特殊的过滤逻辑非常痛苦。FPGA 的灵活之处在于,MAC、PCS/PMA 这些底层协议栈可以交给硬核 IP 或经过充分验证的软核 IP 去实现,而上层的帧处理、交换决策、过滤策略完全可以自己用 RTL 或者 AXI 接口逻辑来控制。

AXI 1G/2.5G Ethernet Subsystem 恰好给了我们这样一种能力。它本质上是把 Ethernet MAC、PCS/PMA、SGMII/1000BASE-X/2500BASE-X 物理层适配、以及 AXI4-Lite 配置管理接口整合在一起。对用户来说,数据面走 AXI4-Stream 接口收发帧,控制面走 AXI4-Lite 寄存器接口,上层逻辑只需要关心帧数据和少量状态信号,不用自己去拼 MAC 帧、不用处理 CRC(IP 内部自动生成和校验)、不用管 8b/10b 编码或者 64b/66b 那些底层的编码规则。

多光口的需求就更直接了。SFP 光模块的个数决定了你能接多少根光纤,但在 FPGA 内部,一个 AXI Ethernet IP 实例只对应一个物理端口。想要四个光口,就要实例化四个 IP。问题来了:这四个 IP 如果互相独立、各跑各的时钟域,那数据交互和同步逻辑就会做得很痛苦。主从级联(Master/Slave Cascade)方案就是解决这个问题的——把其中一个实例配成 Master,其余配成 Slave,让所有实例共享同一套时钟、同一套复位,甚至让 Slave 的收发缓冲区通过级联接口统一管理。这样做的好处是,逻辑上像一个大的多端口 MAC 控制器在运行,而不是四个互相陌生的 IP 在拼凑。

1.2 主从级联模型的本质与误区

先说清楚主从级联到底级联的是什么。AXI Ethernet IP 的主从级联,核心是“时钟同步”和“复位同步”,而不是像某些总线协议那样的“指令级联”。也就是说,Master 和 Slave 之间没有复杂的握手指令队列,它们之间的关系更像是一个时钟源和一群跟随者。Master 实例输出一个gt_rx_clk或者内部参考时钟,通过 IP 的级联端口接到 Slave 的参考时钟输入上,保证所有实例的 MAC 收发逻辑跑在同一个时钟频率和相位参考体系内。这个在做多端口交换的时候尤其重要,因为多个端口之间如果时钟不同步,交换逻辑里做跨时钟域处理就会非常频繁,稍微一个 CDC 保护没写好,就是随机性的丢包和错包。

这里必须澄清一个常见误区:主从级联不等于“Master 帮 Slave 转发数据”。有人以为配好主从关系后,从光口收到的帧会自动从主光口发出去,那是把交换机的转发概念和 IP 的时钟级联概念搞混了。实际上,每个 AXI Ethernet IP 实例仍然是独立的收发通路,帧从哪个光口进来,就会从哪个实例的 AXI4-Stream 接口送出来。真正的帧交换逻辑(例如:从 Port 0 收到的某些帧要从 Port 3 发出去)得你在 IP 外部自己写,用 AXI4-Stream 交叉开关或者自定义的转发引擎来做。主从级联只解决底层的时钟和复位一致性问题,不帮你做交换决策。

在实际的架构设计上,我建议把主从级联结构画成三个层次来看:

  • 物理层:每个光口对应一个 SFP 笼子,FPGA 引脚接到 GT(Gigabit Transceiver)的 MGT 引脚上,光模块的类型决定了线速率——千兆 SFP 走 1.25Gbps,2.5G SFP 走 3.125Gbps。
  • IP 层:每路光口实例化一个 AXI Ethernet IP,所有实例的物理层配置模式保持一致(都走 1000BASE-X 或者都走 SGMII)。
  • 系统层:选定一个 Master 实例,把它的时钟输出通过 BUFG 引到全局时钟网络,再扇出到所有 Slave 的时钟输入端;复位逻辑也以 Master 的复位状态为准,统一生成所有 IP 的复位信号。

这个三分法,基本就是后面所有配置和排查工作的路线图。每一层的问题,都得在这一层解决,不要跨层去瞎猜。

2. 时钟架构设计与复位时序:级联能否跑稳的第一道关口

2.1 时钟分配方案:谁给谁供时钟

如果只做单口,时钟问题很简单——IP 自己根据物理层速率从 GT 恢复出 rx clock,再经过 MMCM/PLL 生成用户侧时钟。但多口级联后,最怕的是每个口的恢复时钟频率有细微偏差,导致跨口的数据交换出现异步 FIFO 溢出或者空洞。主从级联的宗旨就是把“多个独立的时钟域”收敛成“一个主时钟域”。

具体做法是这样的:所有 AXI Ethernet IP 实例的参考时钟(gt_ref_clk)必须来自同一个时钟源。假设板卡上有一个 125MHz 的参考晶振(对千兆和 2.5G 都合适,2.5G 模式下参考时钟走 125MHz,线速率通过 GT 内部倍频到 3.125Gbps),这个 125MHz 先接到一个时钟 buffer,然后扇出到所有 IP 的gt_ref_clk引脚。Vivado 里在约束文件中可以用create_generated_clock和set_clock_groups来保证这些时钟 fan-out 不被工具乱优化。

接下来是级联时钟输出的处理。以 Xilinx 官方文档 PG137(AXI 1G/2.5G Ethernet Subsystem Product Guide)里的描述为基础,Master 实例的gt_rx_clk(恢复时钟)可以直接引出,经过 BUFG 后作为系统侧的同步时钟,并接到每个 Slave 实例的s_axi_aclk或者其他需要同步的时钟端口上。注意这里有个关键细节:如果光模块没有插入、没有建立链路,gt_rx_clk是不会有正常频率输出的。所以链路未建立时,主从级联系统的用户侧时钟会处于非稳定状态,复位逻辑必须考虑这个场景。

我实际配置时的做法是:让所有 IP 实例的axis_clk(AXI4-Stream 接口时钟)统一由 Master 的gt_rx_clk经过 BUFG 驱动。Slave 实例的内部 MAC 收发逻辑会根据各自的 GT 恢复时钟去适应,但对外交给用户逻辑的数据接口时钟是统一由 Master 提供的。这样做的好处是,用户侧的帧处理逻辑只需要面对一个时钟,不需要为每个口各做一个 FIFO 做跨时钟域。代价是 Slave 的数据接口里会有异步 FIFO 来做时钟域转换,这个 FIFO 是 IP 内部自带的,前提是你在配置 IP 时打开了相关选项。

2.2 复位时序的全过程推演

复位是多口级联最容易翻车的地方,没有之一。Xilinx 的 AXI Ethernet IP 对复位时序要求很苛刻:aresetn拉低时,所有时钟必须稳定;复位释放时,必须满足s_axi_aclk已经跑了足够多的周期。在单口场景下,这个时序相对容易满足。但级联场景下,多个 IP 的复位信号如果不同步,会导致各个口的 MAC 状态机互相之间差了若干个周期,表现为链接灯全亮、速率协商都正常,但实际收发包对不上。

根据我个人经验,推荐两个复位策略:

  1. 统一外部复位源。用一个复位控制器(自己写 RTL 或者用 Xilinx 的 Proc Sys Reset IP),上电后先等所有时钟 done,再延时 100 微秒,然后同时释放所有 AXI Ethernet IP 的aresetn。关键是“同时”——这个信号要用同一个寄存器输出,不要分开发,否则 FPGA 布局布线后到达各个 IP 的时间会有差异,极端情况下差的这点时间就够让两个口的 MAC 初始化不同步了。

  2. 利用每个 IP 内部的reset_done信号做菊花链。Master 的reset_done拉高后,再释放 Slave1 的复位;Slave1 的reset_done拉高后,再释放 Slave2 的复位。这种方案的好处是每个 IP 都是在前一个 IP 完全准备好之后才开始初始化,加载更平稳。缺点是整体上电时间变长,而且如果某一个 Slave 因为硬件问题一直不 ready,后面的口就全卡住。

两种方案我最后选了第二种的变体:先用统一复位把所有 IP 拉起来,然后用每个 IP 的reset_done与起来做一个全局 ready 信号,等所有 IP 都 ready 之后再开放用户逻辑的数据通路。这样既保证了初始化一致性,又不会因为某个口出问题就影响全盘启动。

注意:AXI Ethernet IP 内部还有独立的gt_reset(GT 复位)和mac_reset(MAC 复位)。在级联结构中,GT 复位信号最好由同一个复位控制逻辑统一管理,不要在用户逻辑里随便单独去拉某一个口的 GT 复位,否则这个口的 GT 会重新做 PLL 锁定,期间该口的所有收发都是无效的,而且会引入额外的 link drop。

2.3 时钟与复位的约束写法

约束这块,我一开始也吃过亏。Vivado 默认会根据 IP 的约束自动推断时钟关系,但多实例化时,如果不显式声明时钟分组,工具在时序分析时会把所有 IP 的恢复时钟当作相关时钟处理,导致一堆不切实际的时序违例,看着头疼,实际上根本没那回事。

我自己用的约束片段大概是这样的,给你们一个参考:

# 假设 Master 的恢复时钟是 u_master/inst/gt_rx_clk create_generated_clock -name master_gt_rx_clk \ -source [get_pins u_master/inst/gt_rx_clk] \ -divide_by 1 [get_pins u_master/inst/gt_rx_clk] # 所有 IP 的 axis_clk 都由 master_gt_rx_clk 统一驱动 # 需要把这些时钟设为同一组 set_clock_groups -asynchronous \ -group [get_clocks -include_generated_clocks master_gt_rx_clk]

当然,这里面get_pins的路径要根据你的工程层级去调整,不要直接复制就以为能过。关键是思路:把级联后的统一时钟作为唯一的同步时钟组,其余的 GT 恢复时钟如果不需要直接参与用户逻辑的时序约束,可以用set_clock_groups -asynchronous屏蔽掉跨时钟域路径的时序检查。这不是偷懒,是因为这些跨时钟域路径本身就是通过同步 FIFO 或者格雷码同步器来处理的,约束不设反而会爆出一堆假路径错误。

3. 核心配置流程与参数匹配:IP 配置阶段决定后续命运

3.1 AXI Ethernet IP 的配置步骤拆解

在 Vivado IP Catalog 里搜索 AXI 1G/2.5G Ethernet Subsystem,双击打开配置界面。我这里按一个四光口交换机板卡的典型配置来拆步骤,每一步都会附带参数选择的理由。

第一步,选择物理层接口。如果你接的是光模块,通常选 1000BASE-X(千兆光)或 2500BASE-X(2.5G 光)。如果你接的是电口 PHY 芯片(比如通过 SGMII 接口连 RJ45 变压器),那才选 SGMII。这个不能选错,选错了物理层编码方式不同,光模块对端永远协商不上。我自己做的板卡是 SFP 光口,千兆和 2.5G 双速模式,所以选择了 1000BASE-X / 2500BASE-X,这个选项在 IP 里是叫 “1G/2.5G” 的可切换模式。

第二步,设置线速率。如果只是千兆,目标线速率选 1G;如果要 2.5G,选 2.5G。注意 IP 里叫 2.5G,翻译到 GT 层面是 3.125Gbps 的线速率。这个速率与光模块的类型必须匹配——你插一个千兆 SFP 模块,IP 配置却设成 2.5G,那是起不来的,硬件层就协商不了。

第三步,接收/发送数据路径宽度。1G 速率下,用户侧 AXI4-Stream 数据宽度建议选 8 字节(64-bit),因为这样一拍传输就是一个标准的以太网帧片段;2.5G 速率下,8 字节也够用,但如果你想降低时钟频率,可以选 16 字节(128-bit)或者 32 字节。数据宽度越宽,axis_clk可以跑得越低,但内部跨宽度的转换逻辑会增加。从实践中看,8 字节或者 16 字节是多数交换设计的甜点区间,别一上来就选 32 字节给自己找不自在。

第四步,使能管理接口。AXI4-Lite 管理接口默认是打开的,这个必须保留,因为后面的 MDIO 配置、link 状态查询、统计信息读取全靠它。

第五步,处理时钟配置。IP 配置界面里有一项是tx_rx_delay或者类似的 SGMII 时钟模式选择,有不同选项,具体看你的硬件拓扑。如果主从级联,需要在 Slave 上勾选对应的级联选项(具体名称在配置界面底部的 “Cascading” 区域),这一步千万别漏,漏了之后 Slave 的时钟或复位端口就不会暴露出来,你后面想连级联线都没有引脚可连。

第六步,也是很多人忽略的:统计计数器。IP 自带一组以太网统计计数器,比如 RX CRC 错误计数、RX 帧长度错误计数等。多口系统调试时,这些计数器简直是指路明灯。确保在配置界面勾选了 Stats 相关的选项,否则这些寄存器读出来全是 0,排查问题全靠猜。

3.2 主从级联的 IP 实例连接关系

四口系统里,配置一个 Master(比如 Port 0)和三个 Slave(Port 1/2/3)。连接关系上,需要在 Block Design 或者纯 RTL 里做以下几组连接:

第一组,参考时钟连接。每个 IP 的gt_ref_clk都连到同一个外部 125MHz 时钟源。

第二组,级联时钟连接。Master 的gt_rx_clk引出后经过 BUFG,连到三个 Slave 的axis_clk(或者在 Block Design 里自动连接同名总线)。同时,Master 和所有 Slave 的s_axi_aclk也建议统一用同一个时钟,这样管理接口的时序也一致。

第三组,复位连接。所有 IP 的aresetn连到此前提到的统一复位控制器的输出。reset_done信号全部引出,到顶层做一个与逻辑,生成all_ports_ready信号供用户状态机使用。

第四组,数据通路。每个 IP 的s_axis(发送通道)和m_axis(接收通道)分别引出到顶层。如果你想做交换,接一个自己写的 AXI4-Stream Crossbar,或者接 Xilinx 的 AXI Ethernet 的 DMA/桥接方案。如果只是做多口网卡(每个口独立收发),那这四个口的 AXI4-Stream 接口分别接到各自的上位机 DMA 引擎即可。

第五组,MDIO 管理。每个 IP 的 MDIO 接口可以引出到 SFP 模块的 MDIO 引脚。这里有个细节:很多 SFP 模块的 MDIO 是共享总线的,多个端口可以挂在同一个 MDIO 总线上,通过不同的 PHY 地址区分。如果你用的 SFP 模块支持 MDIO 访问,可以把四个 IP 的 MDIO 引脚并在一起,每个 IP 配不同的 PHY 地址,就能用一套 MDIO 总线管理四路光模块。

3.3 速率切换的寄存器操作

IP 配置成 1G/2.5G 双模之后,运行时的速率切换是通过 AXI4-Lite 寄存器来控制的。关键寄存器是 PHY 控制寄存器(或者叫 line rate 选择寄存器)。我做双速自适应的时候,流程是:上电先从 SFP 模块读取速率能力(如果模块支持),然后给 PHY 控制寄存器写入目标速率,再触发重新协商。切换速率之后必须重新读 link 状态,确认 link 起来了再打开数据通路。如果速率切换后没有正确执行复位序列,很容易出现 PCS 层状态机锁死的问题,表现为写寄存器成功了,状态寄存器也显示 speed=2.5G,但 link 就是起不来。

寄存器操作一般用 AXI4-Lite 总线读写,这种操作在 MicroBlaze/Zynq PS 里叫 MMIO,在纯 FPGA 里可以用 AXI VIP 或者自己的 AXI4-Lite Master 去做。推荐大厂的做法:写一段简单的寄存器配置状态机,把初始化序列固化在里面,从复位释放开始,依次配置速率、等待 PCS 锁定、查询 link 状态、上报 ready。这样把问题隔离在初始化阶段,后面跑业务时寄存器基本不用动。

4. 多光口数据面接口:从 MAC 帧到 AXI4-Stream 的完整通道

4.1 帧发送路径与接收路径的行为差异

实际跑数据之前,先理解 IP 的 AXI4-Stream 数据面行为。发送路径s_axis:你往这个接口塞标准的以太网帧(不含前导码和 FCS,IP 自动生成)。当tvalid和tready同时拉高的时钟周期,数据被真正写入发送 FIFO。帧的最后必须用tlast标记,否则 IP 认为帧没有结束,永远不会开始发送。接收路径m_axis:IP 从物理层收到一个完整帧后,会在m_axis上产生一次突发传输,同样以tlast结束。这是一个非常典型的 AXI4-Stream 协议,和 Xilinx 其他 IP 的数据面接口规范一致。

在级联方案中,关键是保证所有口的axis_clk完全同源。我遇到过一种诡异现象:单独调试 Port 0 时好得很,四个口一起打开后,Port 2 的接收偶尔会丢一拍tlast,导致上层 DMA 收到一个错误长帧,然后整个 DMA 通道卡死。查了很久发现是 Port 2 的axis_clk和用户逻辑时钟之间有一个隐含的异步路径,时序约束上没盖到,导致综合工具把跨时钟域的寄存器打拍给优化掉了。后面强制把所有口的数据接口时钟统一约束为同一个时钟组后,问题彻底消失。

不同速率混合的场景也需要提一下:如果你的应用是双速率混合的(Port 0 跑 1G,Port 1 跑 2.5G),那axis_clk的频率实际上是跟随速率变化的。因为 AXI Ethernet IP 在 1G 模式下恢复时钟通常较慢,2.5G 模式下恢复时钟较快。这种情况下,用户侧逻辑最好用独立的固定时钟(比如 156.25MHz),每个 IP 的数据接口通过异步 FIFO 转过来。这个 FIFO 可以自己写,也可以用 Xilinx 的 AXI4-Stream Data FIFO IP,门槛不高。

4.2 以太网帧格式处理及 CRC 的坑

很多人第一次用这个 IP 的时候,会纠结要不要自己算 CRC。答案是:不用。AXI Ethernet IP 的发送路径自动帮你生成 FCS/CRC 并附加到帧尾;接收路径自动剥离 FCS,只把净荷部分送出来。但你得知道,某些教学模式或者 DMA 引擎设计里,用户逻辑可能也需要自己做一次 CRC 校验或生成,这时你可以利用 IP 的统计寄存器来旁路验证,而不是重复计算。

实际测试中,我发现的另一个典型坑是 VLAN 帧。如果你的交换逻辑要处理 VLAN Tag,需要注意 IP 的数据面默认是不解析 VLAN Tag 的,它只会根据配置接收最大帧长内的所有帧。例如,标准以太网帧长是 1518 字节,如果加了 4 字节 VLAN Tag,IP 会把 1522 字节都当作有效帧长度接收;如果你的上层逻辑严格按 1518 校验,就要做对应的长度适配。更坑的是,如果 IP 的最大帧长寄存器设成了 1518,而实际收到的 VLAN 帧是 1522 字节,部分版本 IP 会直接把该帧标记为长度错误并丢弃,表现为对端一直发 VLAN 帧,你这边的接收计数器就是不动。解决办法是在配置界面把最大帧长设大一点,比如 1522 或 1536,或者直接按 4096 来。

4.3 光模块切换与热插拔注意事项

SFP 光模块热插拔是硬件工程师非常在意的事。AXI Ethernet IP 本身不参与 SFP 插拔检测,它只负责物理层收发。但 SFP 插拔会影响光模块的状态引脚(如 LOS、ModDef),这些信号建议接到 FPGA 的普通 IO 监控。当 SFP 拔出时,如果 IP 正在持续发包,GT 输出相当于驱动了一个空的光模块插座,长时间高负载信号反射可能对 MGT 引脚造成反冲(电气上的风险,不是烧毁,但会影响信号完整性)。

我的处理方式是:外部逻辑通过监控 SFP 的 LOS 和 ModDef 信号,检测到拔线后,先暂停发送通道的tready(内部 FIFO 满了自然反压),同时把相应 IP 的发送使能寄存器关掉。等重新插入 SFP 且 link 建立后,再重新打开发送。这个状态机写起来不难,但非常考验对时序的把握——一定要先等 link 状态寄存器变为 up,再让数据跑起来,否则前几包大概率被丢。

5. 级联系统初始化与主从同步启动流程

5.1 分步启动脚本与状态机设计

多口级联系统的初始化,我强烈建议写成一段可重复执行的初始化状态机或者脚本流程,不要靠手动点寄存器。下面是一个经过验证的初始化流程:

  1. 置位所有 IP 的aresetn为低,保持至少 10 个时钟周期。
  2. 释放所有 IP 的aresetn(建议同一个时钟沿释放)。
  3. 轮询每个 IP 的reset_done信号,超时时间为 10ms(这些时间参数在 100MHz 管理时钟下足够宽裕)。
  4. 对每个 IP 配置速率模式(写 PHY Control 寄存器,选择 1G 或 2.5G)。
  5. 等待每个端口的 link 状态变成 up(读 PHY Status 寄存器或者 IP Status 寄存器)。如果 2 秒还没 up,报错并打印对应端口号。
  6. 读对应端口的统计寄存器,做一次清零操作。
  7. 主从所有口都 ready 后,通知用户逻辑打开数据通路。

这套流程中,第 5 步是整个初始化的核心等待点。有些板子插了 SFP 但光纤另一端没接设备,那么 link 状态永远不可能 up。这种场景你要区分清晰:如果系统允许“光纤未接但网口仍能收发包”,那你应该把等待 link up 的阶段跳过,只要求 PCS/PMA 层同步完成。但如果交换机功能和自协商相关,建议把 link up 作为硬性条件。

5.2 组合主从状态:全局一致性如何保证

主从级联最大的隐患是“部分端口工作了,部分端口没工作”,而且这种状态还不是固定的——重启上电后,失败的口可能换了一个。这种随机性往往让人怀疑硬件,其实根因多半在复位同步或者时钟走线上。

为了把不可靠变成可靠,我做了两个额外动作。第一个是在 FPGA 内部引一组调试探测点,把每个 IP 的reset_done、status_link_up、pcs_status抓出来,用 ILA(集成逻辑分析仪)采出来看整个上电时序,确认所有口的启动顺序和时刻是否一致。第二个是加全局一致性检查逻辑:所有端口的reset_done使用与门,产生all_ready;所有端口的link_up也做一层聚合,用户逻辑只有在all_ready有效后才开始查询 link 状态。这样一来,即使某一个口的 GT 锁定比别的口慢了几百微秒,也不会影响其他口的数据处理。

这种设计看着简单,但效果非常明显。我之前有一块板子,每次冷启动后 Port 2 都大概率不工作,用 ILA 抓时序发现它的gt_reset_done比其他口晚了将近一毫秒,原因竟然是该口的参考时钟走线较长,PLL 锁定时间偏慢。如果按原来那种“全部复位释放就跑”的设计,Port 2 的初始化命令早就发出去了,但它内部还没准备好,命令自然被吞掉。改用all_ready门控后,系统会等 Port 2 彻底 ready 再开始后面的配置,问题就再也没出现过。

5.3 验证数据通路的自测方案:从回环到互发

初始化搞定后,第一件事不是接真实业务,而是做数据通路确认。我用过三层递进的自测方案,推荐给大家:

第一层:每端口内部回环。使用 AXI Ethernet IP 自带的外部回环寄存器(寄存器地址在 PG137 里有写),在 MAC 层面或者 PCS 层面做回环。这个回环测试通过,说明该口的发送数据被 IP 内部成功接收并返回,验证了 IP 自身的收发链路。

第二层:光纤对连回环。用一根光纤把 Port 0 和 Port 1 连起来,在这两个口之间做互发。方法是在 Port 0 的发送通道发一个标记帧,Port 1 的接收通道应该收到一个正确帧。反过来再发一次,确认双向都 OK。这个测试能验证两个 IP 实例之间通过用户逻辑搭的桥是否正确——因为你们最终做交换功能时,接收和发送肯定不是同一个口,这个互发是最基本的路由验证。

第三层:对接外部设备。接真实电脑或者交换机,用tcpdump/ Wireshark 抓包确认帧格式没有异常。这一步建议从二层 ping 开始,不要一上来就发大流量,否则出问题都不知道从哪里查起。

三层自测跑通后,级联系统的底子就算稳了。注意,每一层跑通后要把日志或者计数器值记录下来,留存备查。后面如果业务运行中出问题,这些基线数据可以帮助快速区分是链路问题还是用户逻辑问题。

6. 常见问题与排查技巧实录

6.1 链接能 up 但收发互不通信:MDIO 与 PCS 状态的排查路径

这个问题我刚开始做级联时几乎天天遇到。单个口单独测一切都好,一旦级联后,有的口 link 灯亮,但数据就是不通。我的排查路径是固定的,直接抄代码到板子上也适用:

第一步,读 IP 的 PCS/PMA 状态寄存器,确认在物理层已经完成同步且没有代码错误。如果 PCS 状态正常,说明物理层收发过程是无误码的。 第二步,读统计寄存器中的 RX 帧计数。如果 RX 帧计数在增长,说明有帧进入 IP 且被正确解析了——问题多在用户逻辑,比如 AXI4-Stream 接口你没有去消费数据,导致内部 FIFO 满,后续帧直接被丢了。如果 RX 计数为零,再查物理层——查 LOS 信号、查光模块是否有光输入。 第三步,检查 MDIO 访问是否正常。很多 SFP 模块的 EEPROM/PHY 状态是通过 MDIO 或者 I2C 访问的,如果 MDIO 地址配置错了,读出来的状态全是错的值。我早期踩过一个坑——四个 SFP 的 MDIO 地址冲突,导致每次查询 Port 3 的状态,实际读到的却是 Port 0 的状态,调试指令全部打在错误的口上。

链路能 up 但数据不通,十有八九是逻辑层问题;统计计数器不会骗人,学会读统计寄存器是解决这类问题的第一把钥匙。

6.2 CRC 错帧率偏高:信号完整性与跨时钟影响的取舍

CRC 错帧率偏高分两种情况。一种是个别端口长期高错帧率(比如每 10000 帧错 1 个),另一种是系统跑一段时间后突然高错帧率。前者的根因往往在硬件:GT 引脚附近的电源噪声、参考时钟抖动超限、SFP 连接器接触不良。后者则高度怀疑是时钟配置问题——当数据接口的axis_clk与 GT 恢复时钟之间存在未正确约束的异步路径时,某些时钟周期下的数据采样会产生亚稳态,导致帧内的某些字节被采错。

在排除软件问题之前,先做一次电气常规检查:确保 GT 参考时钟的峰峰值抖动不超过该速率档位的规格,确保每个光口的电源独立滤波(不要让四个 SFP 全用同一组 LDO,启动瞬间电流拉低电压会导致信号质量劣化)。这些板级因素确认无误后,再回来看逻辑——多数情况下,把系统时钟换成主时钟源的gt_rx_clk(而不是外部随便一个固定时钟)能明显改善 CRC 稳定性,因为收发天然同步,连异步 FIFO 的环节都省了。

6.3 只有第一个口能工作:级联信号遗漏与地址冲突的记录

多口系统中出现“只有第一个口能工作”,基本就是主从级联配置遗漏了。检查三个方面:一是 Block Design 中是否把所有 Slave 连接的级联时钟都真正连上去了;二是复位逻辑是否把每个口的aresetn都产生了;三是地址映射是否冲突——如果你把四个 IP 的 AXI4-Lite 地址设成了同一个地址,那么访问语义冲突,后面的口完全没法独立操作,表现就是只有地址最靠前的口能正常收发,其他口没有正确的寄存器配置。

这里也要说一句:Xilinx 的 AXI Ethernet IP 不同版本对主从级联的支持程度略有差异,有的版本能在 GUI 里直接配置级联时钟输出,有的版本需要在最终生成的 RTL 里手动修改。遇到 GUI 里找不到 Cascade 选项的,建议好好读一读对应版本的 PG137,找到该版本支持的级联相关端口名称,再想办法手动补连。版本不同,端口名可能叫gt_rx_clk_int、axis_clk或者别的,别拿老经验硬套新版 IP。

6.4 常见问题速查表

现象可能原因检查方法解决手段
单口 link 不 up光模块速率不匹配 / 光纤没插好读 PCS 状态、查 LOS 信号换速率配置、换光模块、重插光纤
多口中只有一个口工作级联时钟遗漏 / 复位不同步 / 地址冲突ILA 抓 reset_done 和 clocks;查看 AXI 地址表补连级联时钟;统一复位;改地址映射
link up 但收不到数据用户逻辑不消费接收数据 / FIFO 满读 RX Frames Count 统计寄存器检查 AXI4-Stream 接口拉高 tready 条件
长时间运行后死口跨时钟域路径未约束 / FIFO 溢出时序报告排查异步路径;查看 FIFO 空满状态用 set_clock_groups 约束;修复用户逻辑反压
CRC 错误频繁硬件信号完整性问题 / 数据路径亚稳态观察掉帧数量与特定端口相关性检查电源、参考时钟;调整时钟方案
MDIO 读回数据异常PHY 地址冲突 / 总线时序不满足逐一访问各个地址,读 ID 寄存器比对修改 PHY 地址配置;调整 MDIO 时钟分频

6.5 进阶调试心得:统计计数器与 ILA 结合用

最后分享一个我自己摸索出来的高效调法。面对多光口交换系统,最怕“现象一致”的假象——你以为问题出在 Port 2,实际上 Port 2 的 MAC 层状态完全健康,只需要查看 Port 2 的 RX 统计寄存器,发现 RX FCS Error 和 RX Frame Too Long 的数量在跟风流增长,大概率就是对端发来的帧格式跟你预期的不一致,或者光模块协商出来的速率不对。充分利用协议 IP 自带的统计计数器,可以省掉大量盲猜时间。

统计计数器的地址表也在 PG137 里,一般从基地址 + 0x300 开始,按偏移排列。用 AXI4-Lite 寄存器写一段 burst 读函数,把每个口的统计信息全部 dump 出来,按口编号列成表格,一次对比分析。这个习惯形成后,多口调试效率提升非常明显,以后不管做四口还是八口,都能用同一套思路快速定位问题。

7. 级联方案的扩展与后续演进思路

7.1 从四口到八口/十六口的扩展要点

级联方案不是只服务固定口数。想从四口扩到八口,核心思路不变,但要注意两个新问题。第一,时钟扇出变了:八路 GT 参考时钟的扇出能力有限,如果板卡上只有一个 125MHz 晶振直接扇出八路,每一路的抖动性能会下降。做法是加时钟 buffer 芯片(比如 LMK00308),一路输入多路差分输出,保证各路 GT RefClk 的质量。FPGA 内部也要注意 clock region 的分配,尽量让每个 GT 所在 bank 都能覆盖到时钟信号。

第二,复位逻辑的粒度要细化,最好按 bank 或按 quadrant 分区域管理。八口以上如果真的一起复位,瞬间的电流冲击可能让某些口的 LDO 电压跌落,反而不利于稳定。按区域逐级复位,配合reset_done门控,是规模上去后的必然选择。

7.2 结合自定义交换逻辑的前景

多光口级联的意义在于给自定义交换机逻辑打好了物理层基础。有了这套底子,你可以在用户逻辑里实现基于目的 MAC 地址的二层转发、基于优先级标记的 QoS 队列调度、甚至端口镜像和流量统计。这些都只是写 AXI4-Stream 逻辑的问题,不再受物理层复杂度掣肘。

我自己在四光口级联的基础上做过一个简化版二层交换机内核:每个口一个收队列、一个发队列,中间用一个简单的 HASH 表做目的 MAC 到出端口的映射。实测下来,在包长 64 字节的极限小包场景下,四口全速收发能跑满千兆线速,靠的就是级联时钟统一后,跨口转发的延迟被压缩到了几十个时钟周期以内。如果你后续也要做类似的功能,物iusically的第一步就是把主从级联这套时钟和复位底子打牢。

7.3 光口介质类型与速率差异的适配

不同光模块有不同的介质类型,比较常见的是多模(MM,850nm 波长)和单模(SM,1310/1550nm 波长)。这跟 IP 配置没有关系,是纯硬件选型的问题,但会影响你的调试——比如测试时如果手头只有单模模块,但跳线用的是多模的,传输距离和误码表现都会受影响。建议在板卡设计时就把模块类型和线缆类型记录下来,调试时对板对应,避免“看起来是 CRC 问题,实际上是光模块和光纤类型不匹配”。

速率方面,如果四个端口想分别承载千兆和 2.5G 混合速率,那么一定要做好跨速率时钟域处理。我上一节提到的异步 FIFO 方案是必须的,并且 FIFO 深度要根据最大帧和背靠背突发帧去计算。我之前配 2.5G 端口接收、1G 端口发送,中间如果 FIFO 太浅,遇到 64 字节小包洪峰时就会丢包。经过压测调整后,FIFO 深度从 512 加深到 2048,背靠背突发 10000 帧的测试才完全干净通过。

8. 写在最后的实战建议

多光口 FPGA 网卡/交换机项目,技术本身不难,难的是细节的层层累积。很多朋友做单口的时候一切正常,一扩到多口就翻车,根因往往就是小看了时钟同步、小看了复位时序、小看了级联信号的完整性。把这几个基础打牢,后面的数据通路逻辑开发会顺很多。

我个人最想强调的一点是:做这种多口系统,一定要养成“分层排查”的习惯。物理层问题就在物理层解决——查光模块、查 GT 锁定、查 PCS 状态;数据面问题就在数据面解决——查 FIFO、查 AXI 握手、查统计计数器。千万别遇到问题就一股脑儿把所有寄存器都改一遍,那样只会把系统调到更乱。

如果你们正在做类似的板子,或者正准备用 AXI 1G/2.5G Ethernet Subsystem 跑多光口方案,希望这篇笔记能给你省下至少一周的调试时间。最后再分享一个压箱底的小技巧:每次调试前,记得先把所有口的统计计数器清零,然后跑一轮固定帧数的压力测试,再统一读回来对比——增量数据远比绝对数据更能暴露问题。祝大家的光口全部绿灯。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询