
最近在做一块多光口板卡核心需求很简单FPGA上扩展出8个SFP光口既能当多口网卡用也能做成轻量级二层交换。项目里最绕不开的一步就是基于Xilinx的AXI 1G/2.5G Ethernet Subsystem做多端口级联配置。网上关于这个IP的教程大多是单端口收发一旦涉及主从级联、多光口、共享GT资源资料就变得很碎我这次也是边查文档边踩坑把过程完整记录一下。如果你正要拿FPGA做多光口网卡、交换机、或者任何需要多个以太网口的系统这篇文章把主从级联的配置方法、时钟复位拓扑、GT位置规划、以及实际调试中容易出问题的点一次讲清楚。适合有一定Vivado基础、但对多端口级联还不太熟的工程师照着做能省下不少试错时间。1. 多光口场景与主从级联机制1.1 两种实现路线多独立核还是多端口级联核先说思路。FPGA上做多光口第一反应往往是“几个光口就例化几个Ethernet核”一个SFP对应一个独立IP然后在外围用自研逻辑把所有口的数据汇总起来。这个思路本身没错问题是当你真正开始布局布线、调时序的时候会发现自己被GT资源和时钟资源卡得很难受。每个AXI 1G/2.5G Ethernet Subsystem的物理层都要占用一组GT收发器而GT不是随便放的它必须落在特定的Quad内参考时钟也得从对应的MGTREFCLK引脚进来。如果你每口一个独立IP、每个IP各自挂独立的GT参考时钟板上就得铺好多组差分时钟线PCB走线、时钟芯片、电源纹波每一项都会变成隐患。更麻烦的是复位和时钟树。独立IP各自产生各自的复位逻辑、各自的管理时钟彼此之间没有同步关系一旦你需要在多个端口之间交换数据跨时钟域同步的工作量直接翻倍。所以多光口场景更推荐的是使用这个IP自带的多端口级联能力。AXI 1G/2.5G Ethernet Subsystem在一个IP内部就可以配置成多个端口端口之间通过级联数据通道Cascade Channel互连主核Master与从核Slave之间形成固定拓扑。从逻辑的角度看它仍然是一个大IP但内部已经把MAC、FIFO、以及端口间数据通路都搭好了用户逻辑只需要处理cascade接口不需要分别管理多个独立IP。我给两种方案做了一个简单对比如果你还没开始布局可以直接参考这个选型对比维度多独立核方案多端口级联方案GT参考时钟每个核独立refclkPCB布线压力大主从核可共享GT Common资源时钟树简单复位与时钟域每个核独立复位跨核同步复杂主核统一复位从核跟随天然同频同相端口间数据交换需外部FIFO加跨时钟域处理通过Cascade接口直连省逻辑资源占用每个核重复例化公共逻辑浪费更多公共逻辑集中整体资源更省时序收敛难度高多个独立IP跨时钟域约束麻烦低内部路径设计好约束集中灵活性单核独立可控方便单独复位依赖主从关系复位和配置需一起考虑从我这次实际测试看8光口场景下多端口级联方案在LUT和FF占用上能省下15%到20%时序也更好收敛。单独复位一个端口的需求基本不存在因为光口网卡也好、交换机也好端口间总是要交换数据的级联方案天然更合适。1.2 主从级联的内部数据流向先理解级联模式下数据是怎么走的。IP内部做主从配置后主核负责和GT Bank的公共资源打交道包括GT参考时钟、GT复位、以及物理层状态机。从核则不直接使用GT Common而是通过主核提供的时钟和复位工作。具体到接口层面主核和从核之间会多一组级联接口这组接口并不是标准的AXI4-Stream而是IP核内部定义好的专用数据通路。每个端口的收包数据会经过MAC校验、FIFO缓冲之后通过这组级联接口送到另一侧。你在Block Design里面连接主从核时实际上就是把这组cascade接口相连形成数据转发通路。需要注意这种级联不是一个完整的交换结构它更像是“把一个端口的数据原封不动地搬到另一个端口”。真正要做L2交换MAC学习、VLAN转发、广播泛洪还要在级联接口的外面接自己的交换逻辑。这点我在后面第3章细讲。2. Vivado中的级联配置实操2.1 IP核参数与共享逻辑配置打开Vivado在IP Catalog里搜“AXI 1G/2.5G Ethernet Subsystem”双击进入配置界面。第一页最关键的是Number of Ports这里直接选择2或4对应两个光口或四个光口。如果光口数量超过4个比如我做的是8个口那就例化两个4端口IP或者一个4端口加两个2端口再通过外部逻辑把多个IP级联起来。单端口和双端口的配置界面差别不大但有一个参数直接影响后续级联能不能通就是Shared Logic选项。这个选项有三种模式Shared Logic including GT主核使用包含GT Common、GT复位、参考时钟处理等公共资源。Shared Logic including GT and peripheral更彻底的主核模式连外设侧的时钟和复位也统一管理。Peripheral only从核模式不使用GT Common依靠主核提供的逻辑。我在4端口配置里端口0和端口1作为一组主从端口2和端口3作为另一组主从分别配置好共享逻辑。这里建议主核选“Shared Logic including GT and peripheral”从核选“Peripheral only”尽可能把公共资源集中在主核减少从核的外部依赖。配置页面里还有一个容易忽略的地方就是Physical Interface。多光口场景一般用1000BASE-X或2500BASE-X这对应SFP光模块的千兆和2.5G速率。如果你只是想在板内测试也可以选SGMII但SGMII是串行MAC-PHY接口和光模块需要的1000BASE-X不是一回事别选错。2.2 级联接口连接与Block Design搭建创建了IP核之后如果选了多端口模式Block Design里会自动出现一组级联接口名字类似axi_cascade_tx和axi_cascade_rx。主核和从核之间就是通过这些接口互连的。手动连接时只需把主核的输出接到从核的输入从核的输出接回主核的输入形成一条双向链路。具体在Vivado里操作是这样的拖入一个IP核配置成4端口端口0/1一组主从端口2/3一组主从主核共享逻辑选最全的模式。再拖入同样配置的第二个IP核如果希望整个系统更一致主核都选同一个共享逻辑配置。使用Make Connection或者手动连线把端口0的cascade_tx连到端口1的cascade_rx反过来也连上。把每个端口的AXI4-Stream接口接收和发送引出去接到用户逻辑或DMA。管理接口AXI4-Lite按需引出每个端口有独立的寄存器空间用来查询链路状态、配置速率等。下面是我实际在Block Design里连线的伪代码示意你可以参考这个思路去连你自己的工程# 创建主从IP set eth_primary [create_bd_cell -type ip -vlnv xilinx.com:ip:axi_ethernet:7.3 axi_ethernet_primary] set eth_slave [create_bd_cell -type ip -vlnv xilinx.com:ip:axi_ethernet:7.3 axi_ethernet_slave] # 主核配置为shared logic including GT set_property -dict [list \ CONFIG.NUMBER_OF_PORTS {1} \ CONFIG.PHYSICAL_INTERFACE {1000_BASE_X} \ CONFIG.SHARED_LOGIC {Shared_logic_including_GT} \ ] [get_bd_cells axi_ethernet_primary] # 从核配置为peripheral only且不单独使用GT资源 set_property -dict [list \ CONFIG.NUMBER_OF_PORTS {1} \ CONFIG.PHYSICAL_INTERFACE {1000_BASE_X} \ CONFIG.SHARED_LOGIC {Peripheral_only} \ ] [get_bd_cells axi_ethernet_slave] # 连接级联接口 connect_bd_intf_net [get_bd_intf_pins axi_ethernet_primary/axi_cascade_tx] \ [get_bd_intf_pins axi_ethernet_slave/axi_cascade_rx] connect_bd_intf_net [get_bd_intf_pins axi_ethernet_slave/axi_cascade_tx] \ [get_bd_intf_pins axi_ethernet_primary/axi_cascade_rx]这一步做完了级联逻辑的骨架就出来了。如果你用的是4端口IPIP内部已经把端口0/1、2/3的级联关系封装好了不需要你手动去连只有跨IP级联才需要手动接。2.3 时钟与复位拓扑设计级联能不能稳定工作一半取决于时钟复位拓扑。我在实际调试中最深刻的体会是多光口系统里时钟规划比逻辑设计更容易翻车。AXI 1G/2.5G Ethernet Subsystem的时钟分两个层次GT参考时钟和MAC侧的用户时钟。GT参考时钟从MGTREFCLK引脚进入GT Bank经过GT Common分频之后给收发器提供高速串行时钟。MAC侧的用户时钟来自GT恢复时钟或内部PLL频率等于线速率对应的位宽比比如1G时125MHz、2.5G时312.5MHz具体与数据位宽有关。主从级联模式下从核不要接独立的GT参考时钟直接用主核的GT Common输出。这样从核的TX/RX和主核共享同一份时钟源在数据从主核cascade到从核时不需要做复杂的跨时钟域处理。如果你给主核和从核各接了一个参考时钟源哪怕板上两个晶振标称频率完全一样实际相位噪声和频差也会导致级联数据偶发错位。复位方面IP核会输出一个mmcm_locked或dcm_locked信号这个信号应该作为主核和从核的全局复位源。从核的复位逻辑不要再单独例化直接使用主核复位分发出来的信号即可。很多新手在级联不工作的第一步就是错误地给从核单独加了一个复位模块导致从核的GT状态机要求和主核保持同步但复位不同步最终链路起不来。下面是一个简单的主从复位连接示例// 主核锁定信号作为全局复位 wire locked; wire sys_reset_n; assign sys_reset_n locked; // 同步后使用 // 从核不使用独立GT复位直接引入主核的复位域 // axi_ethernet_slave 的 gt_reset 和 axi_ethernet_primary 的 gt_reset 保持同一来源这里要强调的是Vivado综合后主核的GT复位是公用的从核侧的相关初始化时序必须和主核的复位释放逻辑一致否则从核内部状态机跑飞了你在外部几乎看不出来只能通过抓内部信号发现。3. 关键细节GT位置、光模块与数据通路3.1 SFP光口引脚规划与GT Bank约束如果把时钟拓扑比作多光口系统的血管GT位置就是骨架。PCB上每个SFP笼子接到FPGA的哪一对GT引脚必须在Layout之前就规划好因为FPGA引脚一旦分配后续想挪非常痛苦。GT收发器是以Quad为单位的一个Quad包含4个收发器MGTY、一个GT Common、以及一对参考时钟引脚。多端口级联模式下建议把主核的GT放在一个Quad的中间位置参考时钟放在这个Quad的MGTREFCLK0或MGTREFCLK1上从核的GT不要跨到另一个参考时钟域除非你明确做了跨时钟域处理。以8光口为例如果FPGA资源允许优先选一个既有8个GT、又有至少两组参考时钟的Bank区域来布局。每组参考时钟负责一组4个端口这样两个4端口IP各自有独立的参考时钟物理隔离清晰调试也好排查。引脚约束的XDC写法大致是这样# 主核参考时钟 set_property PACKAGE_PIN AE12 [get_ports refclk_p_0] set_property PACKAGE_PIN AE13 [get_ports refclk_n_0] # 从核GT位置和主核在同一GT Bank不同Quad或者相邻Quad set_property PACKAGE_PIN AB8 [get_ports sfp_rxp_0] set_property PACKAGE_PIN AB9 [get_ports sfp_rxn_0] set_property PACKAGE_PIN AA8 [get_ports sfp_txp_0] set_property PACKAGE_PIN AA9 [get_ports sfp_txn_0]这里最关键的教训是绑定GT位置时必须把TX和RX的P/N差分对绑定在同一个Quad内不然GT布线绕线很长信号质量会变差。而且参考时钟和对应的GT收发器必须是在同一个Quad内这是硬性要求不能跨Quad共用MGTREFCLK。3.2 光模块适配与物理层配置多光口系统的另一个坑是光模块选型和物理层配置的匹配。SFP插槽可以插千兆SFP光模块、百兆光模块、甚至是电口模块但AXI 1G/2.5G Ethernet Subsystem不一定全部兼容。1G速率下推荐使用1000BASE-X物理接口配置为Auto-Negotiation关闭强制1000Mbps全双工。2.5G速率下物理接口选2500BASE-XSFP模块建议用千兆或2.5G的多模光模块线速率要和IP配置一致。有些SFP模块不带CDR时钟数据恢复有些则带CDR。对于不带CDR的光模块GT接收端的RX均衡参数要额外调整否则高速率下误码率会明显升高。这个参数在Vivado里可以通过动态重配置接口或静态约束设置具体值是GT内部寄存器不同芯片系列不一样建议先用IBERT测试一下通道余量。3.3 数据通路与交换逻辑的对接级联端口之间的数据通路一旦通了接下来就是用户逻辑的对接。AXI 1G/2.5G Ethernet Subsystem对外暴露的是AXI4-Stream接口发送通道和接收通道都带TUSER、TKEEP、TLAST等信号。这里最常见的错误是忽略TUSER的含义。在AXI Ethernet IP中TUSER用来标记帧的起始位置和校验状态不同版本IP定义不一样。有的版本TUSER[0]表示帧起始有的版本TUSER[0]表示错误。接数据之前建议先读一下对应IP Product Guide里TUSER的位定义别把帧的起始位置搞错否则出来的报文全部错位。多光口交换逻辑的对接我通常这样设计每个端口接收通道进入一个独立的异步FIFO隔离恢复时钟和用户时钟。FIFO读出后进入一个简单的查找表模块根据目的MAC决定转发到哪个端口。查不到目的MAC时泛洪到其他所有端口这是二层交换的基本行为。发送通道从各端口的发送FIFO中选路仲裁逻辑保证同一时刻只有一个源往某个端口写。这些逻辑在AXI Ethernet IP外面实现和级联内部无关。级联只是帮你把多个端口的MAC和物理层封装好真正的交换行为还在你自己的逻辑里。4. 常见问题排查与经验4.1 级联口Link不起来的排查这是第一块最容易踩的坑也是我在多个项目里反复遇到的问题。现象是光模块插好光纤插上对端也up了但本端link始终为down或者link偶尔能起来很快又掉。排查顺序我建议按下面的表格来别一上来就怀疑代码症状可能原因排查步骤Link完全不起来主从核共享逻辑配置不对查看主核Shared Logic是否包含GT从核是否为Peripheral onlyLink起来但持续闪断GT参考时钟相位噪声大或频偏用IBERT看通道误码先排除物理层问题Link起来但抓不到包TUSER/TLAST处理错误抓AXI4-Stream接口确认帧起始和帧结束信号时序从核经常掉link复位顺序问题确认从核复位是否跟随主核是否有独立复位导致状态机失步只有某个特定SFP口不linkGT位置或光模块问题换一个SFP口交叉测试确认是板级还是逻辑问题我调试时最常用的一招是先写一个最简单的内部回环测试程序绕过SFP光模块直接通过GT的PCS回环模式把TX数据环回RX端。如果回环能通说明MAC和级联逻辑没问题问题大概率出在光模块、光纤和物理层参数上。如果回环都不通问题就锁定在IP配置或者时钟复位部分。4.2 数据错位与误码率升高多光口跑起来之后最让人头疼的是数据错位。你从端口0发一个特定报文对端端口1收到但内容前几个字节乱了或者整个帧的偏移多了两个字节。这种问题通常有两类来源一类是GT接收端的字节对齐没做好另一类是TUSER/TKEEP处理错误。字节对齐方面AXI Ethernet IP内部有专门的对齐逻辑正常情况下不需要用户干预。但如果你同时接收2.5G和1G的流量IP内部根据线速率自动适配对齐逻辑某些版本IP在速率切换时可能回不到正确的对齐状态。解决办法是速率切换后重新复位一次IP核或者通过寄存器触发接收端的重新对齐。TUSER/TKEEP方面我曾遇到过一个很隐蔽的问题接收FIFO读出的数据宽度是64位但一帧的高32位不一定是有效数据TKEEP的置位也不是固定模式。如果交换逻辑直接把数据原封不动发出去而没检查TKEEP会在短包或者长度不是8字节整数倍的帧尾部产生错误数据。这类问题的排查方法是用ILA抓取任一端口的TUSER和TKEEP波形和IP文档里的时序图比对。确认在TUSER有效的那一拍TKEEP是否也正确地标记了有效字节。如果总是差了一拍那说明你对FIFO深度的设置不合理或者读使能和TUSER之间的拍数关系没对齐。4.3 资源与时序的平衡多光口级联虽然省资源但也不是完全没有代价。我做一个4光口、每个口1G/2.5G可切的交换功能时资源占用大致如下资源类型4端口级联IP估计占用用户交换逻辑估计占用说明LUT25000-350008000-12000实际的MAC和FIFO是大头FF30000-4000010000-15000级联通路里的打拍逻辑BRAM15-25块10-20块接收FIFO和发送FIFOGT4对0每光口1对必须规划好时序方面多端口级联后不同端口的数据通路最终会汇聚到交换逻辑这里容易出现跨时钟域路径。我建议在级联内部和用户交换逻辑之间统一使用一个全局用户时钟域比如250MHz或312.5MHz。接收FIFO的读时钟、发送FIFO的写时钟、查找表逻辑的时钟都用同一个这样唯一需要跨时钟域处理的地方就是FIFO的异步读写端口时序约束和设计都简单很多。如果你发现综合后时序路径比较紧张优先检查是否有很多绕线从一个GT Quad跨到另一个GT Quad。FPGA物理上离得越远布线延迟越大如果非要跨Quad中间加寄存器打拍不要直接在组合逻辑上长距离传输。最后再聊聊我自己的一点体会这次做完8光口的主从级联我的一个明显感觉是这个IP的级联功能本身并不复杂真正考验人的是系统设计时候的全局统筹。时钟怎么给、复位怎么分、GT位置放在哪、SFP光口怎么排布这些看似和IP配置无关的问题最后都会在最关键的调试阶段集中找上门。如果让我重新做一遍我第一件事不是打开Vivado而是先把原理图里SFP笼子对应的GT Bank画清楚把所有参考时钟的走线和来源确认好再开始写代码。级联配置只是最后把一切串起来的那根线之前的物理规划和时钟规划才是多光口项目能不能顺利跑起来的地基。另外多光口系统的调试一定是从简到繁先单口回环后双口级联再加满8口压力测试。每一步都确认没问题了再叠加下一层复杂度。跳步调试是FPGA网络项目里最容易浪费时间的行为没有之一。我在这个项目里就吃了好几次跳步的亏最后老老实实一个个口去验证反而很快定位到了问题。