VCU128这块板子,在我手头放了大半年才真正轮到它上桌。VU3P的资源非常充裕,板载PCIe Gen4和高速光口,天生就是做100G以太网数据通路的好料子。这次任务是把100G Ethernet Subsystem IP核跑起来,用QSFP28光模块和交换机或者另一块板打通链路,再通过XDMA把收到的数据搬回主机。这个过程里我踩的坑比预想的多,但也都很有代表性——GTY参考时钟怎么配、QSFP28的LPMode和ResetL该拉高还是拉低、回环测试怎么一步步做,这些经验值得单独写一篇。文章按我实际调试的顺序来,重点讲IP核参数背后的逻辑和上板时怎么定位问题,适合正在用VCU128、或者准备在UltraScale+上做100G的同学参考。
1. 上板之前的方案设计:先把数据通路和IP清单列清楚
1.1 为什么选VCU128跑100G
VCU128用的是Virtex UltraScale+ VU3P,逻辑单元和DSP数量不是旗舰级别,但这块板真正值钱的地方在于高速收发器和PCIe资源配比非常均衡。100G以太网链路是4路25.78125Gbps,对GTY收发器通道数量要求很直接,VU3P的GTY足够用,而且内部BlockRAM和UltraRAM容量能支撑较大规模的帧缓存,数据通路设计起来不用太抠门。
相比以前在Kintex上调1G/10G,100G带来的是一种完全不同的处理思路。线速率上去之后,CPU不能再逐包干预,必须让DMA和描述符机制扛大头。VCU128板载PCIe Gen4 x16接口,理论带宽能够覆盖100G线速,这是我选它而不是随便拿块带光口的开发板的主要原因。另外,板卡对QSFP28的供电和散热设计比较完善,长时间跑压力测试不容易因为电源纹波导致误码,这点在100G速率下非常关键。
1.2 整体数据通路:从光模块到主机需要经过哪些环节
这条链路实际可以分成三层:物理层负责收发,链路层负责MAC/PCS,传输层是PCIe DMA。光模块把光信号转成4对GTY差分电气信号,经过100G Ethernet Subsystem IP核完成PCS编解码和MAC处理,输出AXI4-Stream总线,接着接异步FIFO做跨时钟域缓冲,再进XDMA映射成PCIe DMA描述符,最后写进主机内存。逻辑链路大概是:QSFP28光模块 → GTY收发器 → 100G MAC/PCS → AXI4-Stream → 异步FIFO → XDMA → PCIe Gen4 → 主机内存。
这里最容易低估的是缓存、索引和重组的工作量。100G线速下,一个64字节的最小以太网帧只有约6.7ns的处理窗口,这种包如果让CPU逐包处理,CPU早就挂了。所以我在设计中采用了“缓存—索引—重组”的思路:每个方向维护一个描述符池,收到的整帧以4KB页为单位写进DDR,更新描述符里的长度和状态;主机侧再通过描述符索引把完整的包取走。说得直白一点,硬件只负责把数据块放进指定内存并通知软件,拆包组包的事交给驱动层。这个架构决定了后续IP核选型和时钟规划的方向。
1.3 IP核清单与分工
下面是我在这个项目里实际用到的Vivado IP核清单,它们并不是独立存在的,很多坑就是IP之间的时序配合没做好引发的。
| IP核 | 用途 | 关键点 |
|---|---|---|
| 100G Ethernet Subsystem | MAC+PCS/PMA一体化以太网链路 | 线速率、FEC、回环、统计寄存器 |
| Clocking Wizard | 生成用户时钟与复位同步 | GT参考时钟与用户时钟必须分离 |
| XDMA | PCIe Gen4 DMA数据搬运 | AXI4-Stream和AXI4-Lite两套接口 |
| AXI4-Stream Data FIFO | 跨时钟域缓冲与速率平滑 | 写满保护、异步时钟、包长匹配 |
| ILA | 片上逻辑分析 | 采样深度、触发条件、信号数量 |
| VIO | 在线读写控制寄存器 | 快速拉引脚、复位模块、检查状态 |
这套组合是Xilinx高速数据通路的常见用法,但并不意味着自动就能工作。最关键的是理解它们之间的依赖关系,尤其是时钟和复位,后面我会单独展开。
2. 100G以太网IP核配置:参数背后是行为和代价
2.1 MAC/PCS/PMA一体还是分拆:配置界面里怎么选
Xilinx的100G Ethernet Subsystem IP核有三种常见形态:单独的100G MAC、MAC+PCS/PMA一体、单独的PCS/PMA。如果目标是对接标准以太网设备,比如交换机的100G口,我建议直接用MAC+PCS/PMA一体化配置,省去自己拼外部PCS的麻烦。如果只是想在FPGA之间做私有高速互联,不在乎协议兼容性,用PCS/PMA或者干脆用Aurora会更简单。这个选择要提前想清楚,因为后续调试方式完全不同。
配置界面里第二个容易迷惑的是FEC选项。如果对端交换机开启了RS-FEC,本地也必须配置成对应模式,两边不一致的话,光口Link状态看着正常,但上层帧校验会疯狂报错。FEC模式有CL108和CL127两种,具体用哪个取决于对端设备支持的协商结果。我的建议是先用没有FEC的模式做通链路,稳定之后再逐步加上FEC验证误码率提升效果,这样能减少同时排查多个变量的压力。
接口位宽和时钟也是配置的必选项。512-bit用户接口在100G速率下的时钟大概是200 MHz量级,这个值IP核会自动计算,生成工程后会通过约束文件告诉你应该跑多少。不要手动改接口位宽去“优化”时序,一旦改了,IP内部的PCS和MAC带宽匹配关系就可能出问题,后面查起来非常难受。
2.2 时钟方案是第一步,Clock Wizard不能随手配
100G以太网对时钟的要求比10G苛刻得多。GTY收发器的参考时钟必须来自专用的MGTREFCLK引脚,不能从普通逻辑时钟转过来。我第一次做的时候就犯了想当然的错误:直接用Clocking Wizard生成一个频率给GTY做参考,结果QPLL一直失锁。后来查原理图,确认VCU128板上有专门给QSFP28光口用的可编程时钟芯片,默认输出频率和IP核要求的GT参考时钟频率匹配,把源切到专用引脚之后,PLL很快就锁上了。
用户侧时钟则是另一回事。100G Ethernet Subsystem IP核的AXI4-Stream接口时钟通常由内部逻辑供给,这个时钟可以用Clocking Wizard生成,但必须和GT参考时钟保持独立。很多人以为反正都是同一个晶振出来的,共用一下没问题,实际上一旦数据通路的读侧和写侧出现频率抖动,异步FIFO就会周期性出现水位异常,表现是偶尔丢帧或者带宽掉一半。时钟方案上偷懒,后面一定会用更痛苦的方式还回来。
2.3 接口模式选错也是经典坑
调过SGMII IP核的人都知道,当IP核和外部PHY芯片配合使用时,FPGA侧必须把SGMII配置成MAC模式,否则两侧都以为自己是PHY,协商永远不会成功。100G Ethernet Subsystem虽然把MAC和PCS/PMA都集成了,不像SGMII那样有明显的MAC/PHY角色二选一,但它在对外对接标准以太网设备时,本质上还是扮演MAC侧角色,这个定位不能搞混。
有同学问我为什么不直接用Aurora 8B/10B或者64B/66B做互联,省去一堆以太网协议的开销。Aurora确实适合板间私有互联,配置简单、时延低,但它和标准以太网不互通。如果另一端接的是交换机或者服务器网卡,就必须用标准的100G以太网IP核。简单说,私有场景用Aurora,公共场景用Ethernet Subsystem,别想着用一个IP吃遍所有场景。
3. QSFP28光模块的管理和回环调试
3.1 先把模块管理引脚和I2C读写摸清楚
QSFP28模块不是插上就能用的。SFF-8636规范里的几个控制引脚,上电状态非常关键。我在第一次上电时发现模块完全没有反应,用万用表量了LPMode引脚,发现默认被拉高了,模块一直处于低功耗模式,发送和接收路径全部不工作。解决办法很简单,把这个引脚拉低,让光模块进入正常工作模式。另外ResetL引脚是低有效复位,正常工作时必须保持高电平。
模块管理信息通过I2C接口读取,器件地址是0x50。最开始我建议先用VIO或者简单的I2C控制器读几个关键寄存器,确认FPGA能够和模块正常通信。以bus 2为例,读Identifier寄存器的命令大致是这样:
# 选中低页地址0x00 i2cset -y 2 0x50 0x00 0x00 # 读取Identifier寄存器,QSFP28模块应该返回0x0D i2cget -y 2 0x50 0x00如果系统里不方便用i2c-tools,就通过VIO把I2C总线的SCL和SDA拉起来,手动模拟一个读操作,逻辑完全一样。Identifier返回0x0D说明识别到了QSFP28模块,接下来可以继续看中断状态寄存器、光模块温度和电压等诊断信息。很多时候Link不上并不是FPGA逻辑问题,而是模块自己报错了,提前读状态寄存器能节省大量时间。
3.2 调试次序建议:内部回环、光纤回环、再到对端
把所有模块都接好之后,不要急着连对端设备。我强烈建议按照“内部回环 → 外部光纤回环 → 对端联调”的顺序推进。内部回环是指在100G Ethernet Subsystem IP核的PCS/PMA层使能loopback,数据从FPGA内部发出去马上收回来,完全不经过光模块,这一步能验证MAC和PCS的逻辑是否正常。如果内部回环都收不到数据,问题一定在FPGA内部逻辑,而不是光模块或者线缆。
内部回环通过之后,再上光模块做外部回环。100G QSFP28常见的光模块有MPO接口和LC接口,需要根据模块类型准备对应的loopback线。MPO模块用MPO环回线,LC模块就用一根短跳线把收发路径串起来。外部回环通了的另一个隐藏好处是能验证光模块的收发状态,如果这个环节出现误码,多半是信号完整性或者模块问题。
最后才接对端的交换机或另一块FPGA板。联调时我会先发固定长度的测试帧,用对端统计寄存器确认收到的帧数和发送端一致。如果这一步发现错包,再回到统计计数器和ILA逐段排查。
3.3 上电时序和复位释放顺序不能乱
高速IP核的上电复位顺序,看起来是小事,踩坑之后才知道有多要命。100G这条链路,我的经验是严格按照下面这个顺序:
- 先确保GT参考时钟稳定输出;
- 释放GTY收发器的复位,等待TX和RX的resetdone信号拉高;
- 再释放100G Ethernet Subsystem IP核的core复位;
- 等待IP核状态寄存器里的PLL和链路状态有效;
- 初始化QSFP28光模块,拉高ResetL、拉低LPMode;
- 最后释放用户侧数据通路的复位,AXI4-Stream和DMA才开始跑。
这个顺序不用背,只需要记住一条原则:先时钟,再收发器,再核心逻辑,最后应用数据。任何时候发现Link起不来,优先检查当前卡在哪一步,而不是反复改RTL逻辑。用ILA挂一下resetdone和link status信号,基本一眼就能定位。
3.4 热插拔和静电问题
开发阶段很多人习惯带电换光模块,这种做法在10G时代偶尔还能蒙混过关,到100G就很容易出问题。QSFP28笼子的金手指在热插拔瞬间会产生比较大的电流冲击,轻则模块寄存器读出来是乱值,重则把模块烧掉。我自己就有一次没断电拔模块,结果模块I2C彻底挂死,重新上电也读不到Identifier,最后只能换模块。
所以我的建议是,凡是涉及插拔光模块和光纤,都先把板卡断电,至少也要把模块的电源和控制引脚通过逻辑保持在安全状态。逻辑侧可以做一个模块在位检测,用ModPrsL信号触发中断,模块不在位时自动关断发送路径,这样能大大降低误操作带来的损失。
4. 上板实测:ILA和统计计数器配合排查问题
4.1 ILA探针怎么挂更实用
Vivado的Integrated Logic Analyzer(ILA)是上板调试最重要的工具。100G这条链路的信号位宽很大,典型的是512-bit数据总线加几十个控制信号,不能一股脑全部挂上去,否则布线压力和BRAM资源消耗都非常大。我的做法是先把关键状态信号挂上,比如GTY的resetdone、core_status、AXI4-Stream的tvalid/tready/tlast,以及几个关键的统计计数器,采样深度设成16384,触发条件用rx_tvalid的上升沿。
等基本链路打通之后,再根据实际问题增加信号。例如怀疑接收数据出现CRC错误,就把rx_stats的几个计数器加到探针里,再看丢帧发生在哪个环节。ILA调试的关键不是信号挂得多,而是能定位到具体模块,大面积盲目抓波形只会让每次编译时间成倍增加,效率反而低。
4.2 统计寄存器比抓波形更能说明问题
100G Ethernet Subsystem IP核内置了发送和接收统计模块,分别对应tx_stats和rx_stats。寄存器手册在IP核生成时会附带一份文档,里面每个字段都写得比较清楚,包括帧计数、字节计数、CRC错误计数、过短帧计数等。上板之后先读这组寄存器,能快速判断链路健康状态。
真正排查问题时,统计计数器比ILA更直观。比如接收方向统计中,帧计数在增加但CRC错误也在增加,说明物理层或者PCS层有误码,需要回头看GTY的接收均衡参数或者光模块状态;如果帧计数完全不动,大概率是模块没有进入正常工作模式,或者RX路径的复位没有释放。计数器定位到方向,ILA定位到信号,两套工具配合使用非常高效。
4.3 一次典型问题复盘:模块Link上了,但收不到帧
这个案例我一个下午才排查完,印象非常深。现象是QSFP28模块已经识别成功,GTY的Tx/Rx resetdone都拉高了,IP核状态也显示Link up,可是RX方向统计帧数一直为零。
我先用ILA挂上rx_axis_tvalid和rx_axis_tdata,抓了一段时间发现tvalid有时会拉高一两个周期,但紧接着tlast和tuser出现异常,整个包被丢弃了。这个现象说明物理层已经通了,问题在MAC层成帧或PCS状态机。后来回头看IP核的复位信号,发现RX侧的用户复位一直被外部逻辑拉高,虽然GTY resetdone已经完成,但MAC侧的rx_core_reset还没释放,导致数据到了MAC层之后被内部状态机丢弃。
把复位时序调整成GTY resetdone完成之后再释放rx_core_reset,马上就能看到RX统计开始计数。这个坑的根源就是我在3.3里说的复位顺序问题,其实原理我很清楚,但现场接线和逻辑优先级一多,就容易疏忽。做100G这类复杂链路,复位状态一定要放在最显眼的位置,最好做一个状态寄存器,把每一步的完成状态都记录下来,出问题时先看它。
5. 常见问题与避坑速查表
5.1 光口Link不上,先查这些
做100G高速链路,Link不上的原因很多,我整理了一张速查表,涵盖了最常遇到的几种情况。
| 现象 | 可能原因 | 排查动作 |
|---|---|---|
| Link灯不亮、状态寄存器为0 | QSFP28的LPMode过高 | 检查LPMode引脚,正常要拉低 |
| 模块复位后一直无响应 | ResetL没拉高或模块热插拔损坏 | 量ResetL电平,重新上电拔出重插 |
| GTY PLL锁不住 | GT参考时钟频率不对或来源插错 | 确认MGTREFCLK来自板载可编程时钟芯片 |
| 模块读到Identifier,但TX/RX resetdone不拉高 | GTY通道没有正确连接到QSFP28的对应引脚 | 核对原理图收发器通道映射 |
| Link up但RX统计为0 | rx_core_reset未释放或成帧异常 | 抓ILA看tvalid/tlast/tuser,检查复位顺序 |
| 频繁CRC错误 | FEC模式不一致或光模块光功率异常 | 检查FEC开关,读模块诊断寄存器 |
大部分Link问题最后都能归结到引脚电平、参考时钟、复位时序这三类。出现问题时不要急着改宏定义或者到处加逻辑,先把状态寄存器读一遍,通常能直接定位到具体环节。
5.2 AXI4-Lite寄存器写入没有反应
XDMA的AXI4-Lite接口用于配置用户逻辑和100G IP核寄存器,有时候会出现写入完全没有反应的情况。首先确认AXI4-Lite总线的reset是否已经释放,XDMA上电后需要等待PCIe link up,然后用户逻辑侧复位才会释放。如果复位没释放,总线事务会被直接丢弃。
其次,IP核的寄存器偏移地址必须参考Vivado生成的寄存器手册,不同版本、不同选项的IP核寄存器地址可能不同。我第一次就是凭上一个项目的记忆直接写0x0204,结果那个地址根本不输入法,白白浪费了半天。正确做法是打开IP核生成的regmap文档,找到要操作的寄存器,核对偏移,再检查4字节对齐。非对齐访问会被AXI总线丢弃,这是很常见的低级错误。
如果很难定位,直接在AXI4-Lite从机接口上挂ILA,观察awvalid、wvalid和bready的握手情况,能立刻看出是总线上没有事务进来,还是从机没响应。这个方法屡试不爽。
5.3 异步FIFO和跨时钟域导致的数据错乱
GT参考时钟、用户时钟、PCIe时钟,三个时钟域在100G数据通路里必然交汇,跨时钟域是必须跨过的一道坎。我使用AXI4-Stream Data FIFO来做缓冲,它支持读写侧独立的时钟,配置时要注意FIFO的数据位宽要和上下游接口一致,否则会出现字节错位或者带宽不匹配。
FIFO深度也不是越大越好。深度过大,Latency会明显增加,而且几乎占满整个BRAM甚至UltraRAM;深度太小,在背靠背帧突发时又容易丢帧。按照典型的100G应用,每包最大2KB估算,写侧时钟和读侧时钟频率相差不超过10%的情况下,深度选4096比较均衡,既能吸收突发,也不会带来太多额外时延。实际项目中可以根据发包模式调整,先用大深度跑通,再逐步减小优化资源。
5.4 缓存、索引和重组方向的经验教训
高速数据通路中,缓存、索引、重组这三件事做不好,带宽再高也是白搭。我在调试XDMA搬到主机的数据时发现,收到的包偶尔会出现长度对不上,追查下来是描述符里的长度字段更新晚于数据写完的时钟,导致主机读取时拿到了旧长度。
这个问题的本质是异步场景下状态同步没有做好。我的解决方法是:DMA写完一整个数据块之后,插入一个内存屏障或者状态同步信号,等确认数据落盘完成再更新描述符。听起来很像软件里的缓存一致性,其实硬件上也完全一样。另外,描述符本身要按自然边界对齐,不要用位域跨字节,否则驱动侧解析会越来越复杂,出问题极难排查。
5.5 关于IP核版本和生成工程的坑
最后提醒一句,不同Vivado版本生成的100G Ethernet Subsystem IP核可能存在差异,尤其是寄存器映射和约束文件,最好不要在不同版本之间来回迁移工程。我中途把工程从Vivado 2021.2升级到2022.1,结果IP核的example design自动更新,部分引脚约束和GT参考时钟定义发生了变化,调试问题一下多了好几倍。
如果项目周期紧,就用固定版本,不要随便升级。所有IP核都保持在同一个版本下生成和综合,避免因为工具链差异引入额外变量。这类问题往往看不出逻辑错误,却会让整个上板的节奏被打乱。
6. 几句体会
我在这次调板过程中最大的体会是:100G这类高速接口,90%的坑其实都出在时序和初始化上,而不是数据通路的逻辑设计上。如果再来一次,我一定会把板级时钟、复位顺序和QSFP28模块管理接口这三件事先做完,再开IP核的配置。另外,多做回环测试,从内部回环、外部光纤回环再到对端联调,一层层递进,遇到问题不要急着改RTL,先用ILA和统计计数器把疑点定位到具体模块,往往能省下半天时间。最后再分享一个小技巧:把GTY resetdone、Link状态和复位步骤记录到Debug寄存器里,上板后先读一遍,很多问题在动手之前就已经有答案了。