开篇先交代一个背景:我最近在调试一块基于UltraScale+的FPGA测试板,业务要求是对接100G光模块,把线上真实的100GE流量收进来做报文处理,再发出去。板子回来第一次上电,光模块link不亮,查了一圈发现不是硬件问题,而是FPGA侧PCS的复位时序没给够,这让我意识到,做100G光口测试,真正考验人的不只是光模块本身,而是从光模块寄存器、线缆、SerDes眼图到FPGA硬核整个链路是否对齐。这篇文章就把这次测试的完整过程写出来,从系统构成、光模块控制、FPGA侧工程,到误码测试和踩坑记录,全部按实际执行顺序展开,适合正在做100G光口开发、或者刚接触FPGA高速SerDes的工程师参考。
1. 100G光口测试到底在测什么:链路组成与测试边界
1.1 一条完整的100G链路从物理层到MAC层要经历什么
很多人第一次接触100G光口,以为就是买一个QSFP28模块插到板上,FPGA里开一个IP核,然后就能通。实际上100G光口是一条非常严格的分层链路,任何一个环节掉链子,表现出来都是link不亮、误码率高、丢包严重,但定位起来特别费劲。
先看这条链路的物理组成。QSFP28模块本身是4发4收,也就是4条收发通道。如果走的是100GBASE-LR4,模块内部已经把4路25G信号波分复用成1路,通过单模光纤传输;如果是100GBASE-SR4,那就是4路25G信号直接在多模光纤上并联传输。无论哪种,FPGA和模块之间走的是CAUI-4接口,也就是4条25.78125Gbps的高速差分线。FPGA侧需要有一个硬核或者软核来实现100GE MAC和PCS层,一般Xilinx UltraScale+里叫做CMAC,Intel的叫做E-Tile硬核或者100G MAC IP。
测试时最容易搞混的一点是,我们说的“100G测试”究竟是测光模块本身,还是测FPGA到模块这段的高速电接口,还是测整条端到端的网络链路。三种测试的目标、手段和判断标准完全不同。光模块回环测试测的是光电转换和光纤传输质量,FPGA内部串行回环测的是SerDes和PCB布线质量,端到端测试测的才是整机业务质量。
1.2 测试拓扑和回环方式要提前定好
我的做法是在设计测试用例之前,先把回环方式定义清楚:
| 回环类型 | 回环位置 | 能验证的内容 | 典型误码要求 |
|---|---|---|---|
| FPGA近端串行回环 | GT收发端内部 | FPGA SerDes、时钟、PCS绑定 | BER < 1e-15 |
| PCB走线回环 | 板级走线或连接器 | PCB阻抗、连接器质量 | BER < 1e-15 |
| 光模块电回环 | 模块内部收发短接 | 模块电路、主机侧管理接口 | BER < 1e-12 |
| 光纤回环 | 光纤把TX和RX连接 | 光模块收发、光纤链路 | BER < 1e-12 |
| 端到端业务回环 | 对端交换机/设备 | 整机业务、FEC、上MAC层 | 无丢包 |
注意,不同回环点之间是逐级验证、逐级排除的关系,不建议一上来就做端到端。比如我在测试时先做FPGA串行回环,这一级通常能达到极低的误码率,因为信号根本没有离开芯片;然后做光模块的电回环,如果这里出现误码,说明模块和FPGA之间的CAUI-4电气链路有问题;再然后做光纤回环,如果这里出现误码,说明光模块发送端和接收端问题——注意这里的“接收端”往往包含了模块内部的CDR和DSP,问题未必全部出在光纤上。
1.3 速率关系:为什么25G变成了25.78125G
这是一个让很多刚入门的工程师困惑的点。100G Ethernet的线速率不是精确的25G,而是25.78125G。为什么?因为100GE在PCS层采用了64B/66B编码,每64比特原始数据编码成66比特,编码开销是2/64,也就是约3.125%。相应地,MAC层的速率是100Gbps,经过编码后物理层的速率就要变成100 * 66/64 = 103.125Gbps,再除以4条通道,每条就是25.78125Gbps。
这个速率差会在测试里带来几个实际影响。第一,参考时钟必须是精确的161.1328125MHz(对应103.125Gbps),或者用156.25MHz经过内部PLL倍频,不能随便用一个25M的晶振去凑。第二,流量仪的线速打流如果设置为100G,实际上物理层跑的是103.125G,MAC层才会收到100G的净荷速率。第三,如果使用FEC,还要考虑FEC带来的额外开销和时延,在长距传输场景下这些细节直接关系到链路能否建立。
2. 光模块侧的控制与管理:寄存器访问是第一道门槛
2.1 QSFP28管理接口和I2C地址分配
光模块不是插上电就能用那么简单。在模块正常工作之前,主机必须先通过I2C接口读取模块的相关信息,并确保模块处于正确的配置状态。QSFP28标准规定了一个两线串行接口,地址0x50用于访问低128字节的寄存器空间,地址0x51用于访问高128字节的寄存器空间(比如部分诊断信息和页选择)。
在测试中,我习惯先写一个简单的I2C扫描脚本,用FPGA内部的I2C控制器或者外接的USB-I2C适配器,去读模块的标识寄存器。SFF-8636规范在偏移0x00处定义了模块标识符,QSFP28应该是0x0D;0x01到0x03是连接器类型、收发器编码等;访问这个区域能确认线缆和模块是QSFP28,而不是早期的QSFP+。
对于100G模块测试,有几个寄存器非常关键:
| 寄存器偏移 | 内容 | 测试中的意义 |
|---|---|---|
| 0x00 | 模块标识符 | 确认模块类型为QSFP28(0x0D) |
| 0x03 | 收发器编码 | 区分SR4、LR4、AOC等 |
| 0x0D | 模块温度 | 高低温测试时判断散热是否正常 |
| 0x0E | 供电电压 | 确认模块供电在3.15V-3.45V范围内 |
| 0x16-0x19 | 接收光功率 | 判断光链路是否建立、光功率是否在合理范围 |
| 0x1A-0x1D | 发射光功率 | 判断模块发射是否正常 |
| 0x03-0x06 | 告警标志 | 锁定光链路异常的具体方向 |
| 0x7D | 页选择 | 访问更高页的数据时需先设置 |
2.2 对应I2C读取脚本的编写示例
如果是在调试阶段,我用FPGA建立一个I2C主机,然后通过串口下发读写命令,Python侧快速验证数据合理性。下面是一个简化但可用的示例,假设I2C总线通过FPGA内部的PIO模拟实现,FPGA侧接收模块地址和寄存器偏移,并返回数据:
import time import serial SER_PORT = "COM12" SER_BAUD = 115200 def i2c_read(addr_i2c, reg): cmd = "R {0:#x} {1:#x}\n".format(addr_i2c, reg) ser.write(cmd.encode()) line = ser.readline().decode().strip() # 返回格式示例: "OK 0x1e" if line.startswith("OK"): return int(line.split()[1], 16) return None ser = serial.Serial(SER_PORT, SER_BAUD, timeout=1) time.sleep(0.1) # 读取0x50地址下的模块温度(0x0D) temp_code = i2c_read(0x50, 0x0D) # 有符号数,单位0.01度 if temp_code is not None: if temp_code & 0x8000: temp_code -= 0x10000 temp_celsius = temp_code / 256.0 print("模块温度: %.2f C" % temp_celsius) # 读取0x50地址下的模块类型(0x00) ident = i2c_read(0x50, 0x00) print("模块标识: 0x%02X" % ident)上面的read函数里有一个细节:温度寄存器在QSFP28里用的是16位有符号数,但I2C读取通常是8位一读,所以要连续读两次再拼起来。很多时候你以为读一个寄存器就能解决问题,但实际上光模块的寄存器访问涉及字节序、符号扩展、告警标志闩锁等等,这部分是测试中容易忽略的坑。
2.3 模块复位和低功耗模式的控制
QSFP28的硬件控制引脚有两个特别重要:ModSeL(模块选择)和LPMode(低功耗模式)。模块默认上电后如果LPMode为高,模块会进入低功耗状态,部分功能不工作。很多测试板第一次上电光模块不亮,就是LPMode这个引脚没有正确拉低。
ModSeL用于选中模块,对于单模块应用,直接接地即可;但在多模块设计中,要注意这个引脚不能悬空,悬空的状态不确定,可能选不中模块。
另外还有一个ResetL引脚,低电平有效复位。这个引脚一般由FPGA GPIO控制,在上电后得保持至少2ms的低电平,然后再释放,模块才能正常初始化。我在测试中曾经遇到一个现象,模块偶尔能link成功,偶尔不能,概率性问题,后来用示波器观察ResetL引脚,发现释放时的上升沿有毛刺,再接一个10K上拉电阻解决。此类问题在高速测试中非常典型,软件上看起来是随机故障,根子其实在硬件信号完整性上。
3. FPGA侧工程准备:硬核配置与时钟复位
3.1 用100G硬核MAC/PCS还是用第三方IP
FPGA实现100G光口,首先要决定MAC/PCS用什么实现。Xilinx UltraScale+平台的CMAC硬核是业界最常用方案,集成在GTY/GTM收发器附近,自带100GE的PCS,支持RS-FEC和Fire Code FEC。用CMAC的优势是资源占用极低、代码路径固定、很少有人能靠软逻辑实现同样的性能,所以除非是在做研究性质的实现,否则强烈建议直接用硬核。
但硬核不等于不需要配置。CMAC在例化时需要关注几个关键参数:协议选为100GE、FEC模式(Auto/RS-FEC/Fire Code/None)、线速率25.78125G、参考时钟选择161.1328125MHz或通过MMCM分频。FEC的选择直接关系到链路误码容限,RS-FEC(RS(528,514))纠错能力更强,时延更大,适合长距光纤段短的情况;Fire Code的时延较小,适用于数据中心内部。如果模块和FPGA都在同一个机柜内,距离短到可以忽略,就算不用FEC也可能跑稳定,但标准规范推荐开启FEC。
3.2 参考时钟和复位时序:最容易翻车的地方
100G链路对参考时钟的质量要求非常苛刻。CMAC的参考时钟频率必须精确到数十ppm以内,相位噪声直接决定SerDes的眼图质量。PCB上参考时钟的走线要远离高速信号,时钟芯片的去耦电容要加足,这些是保证测试结果可复现的基础。
复位时序更是关键。我第一次调试就吃了这个亏:复位信号只给了几十微秒,实际上CMAC的复位流程要求,参考时钟稳定后,必须等待PLL锁定,然后依次释放GT复位、PCS复位、MAC复位,每个阶段之间还需要等待状态标志。很多IP核的手册里都写了复位时间要求,但实际做的时候,在代码里加一个简单状态机控制复位释放流程,比靠延时凑数要靠谱得多。
以下是一个CMAC复位时序的简化示例(Verilog思路):
// 复位流程: // 1. 等待gt_ref_clk稳定(通过counter延时实现) // 2. 拉高gt_rx_reset / gt_tx_reset,等待gt Tx/Rx reset done // 3. 拉高pcs_rx_reset / pcs_tx_reset,等待pcs reset done // 4. 拉高mac_rx_reset / mac_tx_reset,等待mac reset done // 5. 复位完成,link可以开始建立 always @(posedge user_clk) begin if (!sys_rst_n) begin rst_state <= RST_IDLE; gt_rx_reset <= 1'b0; pcs_rx_reset <= 1'b0; mac_rx_reset <= 1'b0; end else begin case (rst_state) RST_IDLE: begin gt_rx_reset <= 1'b1; rst_state <= RST_GT_WAIT; end RST_GT_WAIT: begin if (gt_rx_reset_done && gt_tx_reset_done) begin pcs_rx_reset <= 1'b1; pcs_tx_reset <= 1'b1; rst_state <= RST_PCS_WAIT; end end // ... 依次推进 endcase end end注意上面的值不能直接用于实际工程,真实项目里还需要根据具体IP核的datasheet调整复位保持等参数。但核心思路通用,就是“等待状态标志而不是盲目延时”。
3.3 用户侧接口:AXI4-Stream数据通路
CMAC的用户侧通常是512位宽的AXI4-Stream接口,时钟频率约322.265625MHz。也就是说,FPGA逻辑每个时钟周期要处理512位数据。这个接口有几个信号特别容易出错:tkeep、tuser、tlast。
tkeep在不同厂商IP里定义不同,有的IP的tkeep不是每字节有效,而是以4字节为一个粒度,需要判断tkeep的编码方式。tuser里往往包含了包的错误标志、FCS错误标志、EOF和SOF位置。数据通路调试时,建议先用集成式误码仪(IBERT)验证物理层,再用MAC侧的环回(CMAC loopback)验证MAC层,然后把数据通路接入业务逻辑。
如果只是做测试,最简单的数据处理是直接把接收到的AXI数据写进DDR,再由CPU读取分析;或者直接在FPGA内部做一个“第N个包错位重抓”的逻辑,这样能快速定位数据通路上的截断或拼接错误。
4. 百G级误码测试与数据通路验证:从PRBS到满速率跑流
4.1 物理层验证:IBERT和PRBS模式
FPGA高速通道调试,我一般从Xilinx IBERT(集成式比特错误率测试)开始。IBERT不需要配置任何上层逻辑,直接用Vivado Hardware Manager把比特流下载进去,然后打开GT通道的眼图扫描和误码测试界面。
IBERT支持把GT配置成PRBS发生器/检测器,常用PRBS31、PRBS23、PRBS15、PRBS7。PRBS31和PRBS23更接近真实数据的统计特性,适合用来做误码评估。实际测试中,连续跑30分钟、1亿Gbit数据零误码是最基本要求。
但要注意,IBERT的物理层PRBS测试通过,只能说明SerDes和PCB布线没问题,并不能说明MAC/PCS数据通路没问题。原因很简单:PRBS不经过MAC层,数据被直接映射到SerDes,而真实流量要经过PCS编码、FCS校验、MAC地址处理等等。所以物理层测完,还得做上层的数据完整性测试。
4.2 数据通路验证:用已知码流比对
用FPGA内部产生固定报文,经过CMAC发出,再从接收端收回来比对,是数据通路验证的常用办法。比如我常会生成一个递增序列的包号,或者生成一个训练码流(pattern),在接收端检测是否出现数据错位、丢包、乱序。
这个过程里有一个细节:MAC层会插入IFG(帧间隙)、Preamble和SFD,所以接收端比对时不能直接拿“发出数据”和“接收数据”做逐字节比较,而是要解析出以太网帧之后再做比较。如果是纯FPGA内部测试,可以跳过MAC层,直接对PCS层做回环,这样能快速验证PCS内部对齐标记和编码是否正确。
另一个重要手段是在CMAC的发送端和接收端之间插入IP层回环测试逻辑。做法是接收端把收到的以太网包原样转给发送端再发出去,然后把IBERT设置为改PRBS为“自发自收”。这样可以在不需要外部流量仪的情况下,验证整条MAC通路是否存在FCS错误、CRC错误、Oversize包等。
4.3 用流量仪打流:线速和丢包率怎么测
如果条件允许,用Spirent或IXIA这类流量仪做端到端测试是最接近真实业务的方式。流量仪测试有两大重点:线速转发和丢包率。
线速是100G,也就是每秒要发出约1450万线速64字节小包(以64字节为例,100GE线速每秒小包数约为100 * 10^9 / (84 * 8) ≈ 148.8万?这里要注意以太网帧最小64字节,加帧间隙12字节和前导8字节,单帧传输时间是84字节,所以线速小包是每帧672ns,每秒约148.8万帧,线速小包计算确实要按84字节来算,而实际这个值大概在148.8万帧量级。),这个计算可以用在测试准备阶段,用来判断流量仪是否真的跑到了线速、FPGA侧有没有出现长时间阻塞。
丢包率测试建议采用RFC 2544的方法,从小包到大包逐级加流,直到出现丢包,找到FPGA/DDR/CPU处理链路的吞吐瓶颈。100G测试的一个实际困难是,FPGA里如果接了CPU核(比如ZynqMP)并通过了AXI总线和DDR访问,那整机吞吐往往不是100G,瓶颈可能在DDR带宽或缓存一致性上。测试时要把这种瓶颈和光口本身的误码区分开,否则容易误判为光模块问题。
4.4 误码分析和FEC统计
误码分析不能只看最终有没有误码,还要看误码数出现的强度和分布。100G系统的误码来源大致分为随机误码和突发误码,前者的产生和光信噪比、接收灵敏度相关,后者的产生往往和电源噪声、串扰、模块DSP突发错误相关。
如果模块支持FEC功能,FEC纠错计数是一个非常有用的指标。当FEC纠错计数持续增加,说明链路余量在恶化,链路虽然没断,但随时可能断。用FPGA读取模块的FEC纠错统计寄存器,配置一个阈值告警在某次测试中帮我提前发现了光纤被弯折导致的光功率下降问题,比光功率告警来得更早。
5. 实测踩坑记录:这些坑让我多花了一周时间
5.1 光功率“看起来正常”但link不起来
有一次测试中,我读到的模块接收光功率在-6dBm,模块的灵敏度指标是-10dBm,按理说光路没问题,但就是不link。后来查文档发现,这个模块是LR4类型的波分模块,内部有4个不同波长的接收通道,各个通道的光功率是分开的,而我只读了其中一个通道,另外几个通道有一个功率异常低。从此以后我养成了一个习惯,凡是波分类型的光模块,读取接收光功率时一定是把4个通道全部读出来,逐通道对比,不能只看数组里第一个数据。
5.2 CMAC复位标志显示完成,但数据不通
这个坑是让我记忆最深刻的。复位状态机已经等到了所有reset_done标志,link也被本端和对端设备都识别到了,但实际一跑流量全丢。后来打开Vivado的硬件管理器看GT的状态寄存器,发现TX和RX没有出现对齐,submit信号始终没有拉高。
排查到根因的日子我用了快一整天。最终判断是CMAC配置里有一个选项“RX alignment”被设置为了自动模式下,它要求端到端必须有一次“远端故障指示”帧来触发PCS重新对齐,而我做的端到端拓扑里恰好有一台不支持100G标准的开发板,导致这个帧始终没发出来。解决办法是手动触发一次PCS对齐,或者把CMAC的alignment模式改为软件控制。
5.3 高速走线靠近电源层导致眼图边缘
在PCB Layout完成后的测试中,某一组GT通道通过IBERT扫描眼图,发现眼宽比其他通道少了约20%,但还勉强合格。后来量了PCB板上的走线,发现有一条通道的过孔恰好跨过了电源层的一个分割缝,造成阻抗不连续。这种问题在100G这种高速率下会直接表现为误码率不稳定,特别是在温度升高后更加明显。所以,如果你测到某个通道的误码率略高于其他通道,不要急着怀疑模块,先去看PCB Layout里的过孔、换层、走线参考层是不是干净的。
5.4 模块告警闩锁的“假报”问题
光模块的很多告警标志是闩锁的,也就是说,当告警事件发生时对应的bit会被置位并且保持住,即使事件已经消失,bit仍然是1,必须通过写入操作清除闩锁状态。我在测试中遇到过模块一直报“TX fault”,怎么配置都不行,以为模块坏了,后来才发现是上一次测试中TX功率瞬降触发了故障闩锁,模块已经恢复正常,但标志位还停在1。用I2C写入相关控制寄存器清除闩锁状态后,告警才消失。
这段经历说明一个道理:测试时读到的告警状态是“曾经发生过的告警”还是“当前正在发生的告警”,取决于你是否在读取前先做了状态清除。建议在测试程序中,启动时先对所有状态寄存器做一次清闩锁操作,再读取实时值。
5.5 过温环境下误码急剧增加的定位思路
在高温测试环节,我发现模块温度超过65度以后误码开始增多。最初怀疑是模块本身的温度性能不行,但后来用FPGA内部温度传感器和板卡上的NTC比对,发现模块温度和FPGA温度都在正常范围,倒是模块供电电压掉了0.1V左右。追查下去,发现是电源模块在高温下电流能力下降,配合PCB上过细的电源走线,导致供电压降,而模块内部DSP在低电压下高频性能变差,误码自然增加。
这个案例的问题定位思路值得记住:高速误码问题不一定在高速链路上,电源、地的直流稳定性同样会引起高速模块工作异常。排查时要把电源轨实测情况和数据链路并列来看,别一上来就怀疑光口。
6. 从测试结果到量产测试策略的思考
前面写的都是开发调试阶段的测试方法,但如果你和我一样需要把这类100G光口测试延伸到产线环节,需要考虑的问题会更多。
产线测试和研发测试的差异在于:研发测试追求的是深挖问题和边界,产线测试追求的是快、准、省。产线测试不可能像研发那样跑30分钟IBERT,一般会压缩到几十秒内的PRBS测试加链路建立检测,再用光功率和FEC纠错计数做快速判定。
我在产线测试方案里通常设置三个档位的结果判定:PASS、MARGIN、FAIL。PASS是误码率和光功率都远超合格线;MARGIN是接近边界,需要人工复核;FAIL是直接判定不良。同时,产线测试固件必须支持远程控制指令,测试电脑通过串口或者以太网控制FPGA完成所有测试流程,这样可以接入MES系统自动记录结果和序列号,避免人工记录出错。
另外,产线测试的测试项不要设计成“测到通过为止”,而是“测到结果稳定”,一旦出现不稳定结果,立即停止,标记为疑似不良,交给工程人员分析。这样能尽量避免把隐性不良产品流向客户。
还有一个容易被忽略的点,产线上不同的工位之间可能出现同一批模块不一致的问题。我曾经遇到过一批模块在研发工位测试通过,但在产线工位测试失败,最后发现是两个工位使用的测试光纤长度不同,导致光衰减差异。所以产线测试环境的光纤、衰减器、跳线需要进行定期校准和统一规格管理,否则测试结果的可比性会很差。
7. 一些测试工具和调试手法的补充
100G光口测试的工具有哪些值得常备?除了前面提到的IBERT、流量仪和I2C脚本,最好还有光功率计、可调光衰减器、以及可以抓光口电信号的示波器。光功率计用来做光纤链路和模块输出功率的绝对测量,衰减器用来测试接收灵敏度和链路余量,示波器虽然很难直接抓到25G电信号(需要用高速探头),但抓模块的管理信号、复位信号、电源纹波足够了。
调试手法上,我推荐一个做法:在FPGA逻辑里做一个“分步执行寄存器”调试接口,把需要控制的关键信号(GT reset、PCS reset、MAC reset、模块复位、LPMode)全部映射到一组可读写寄存器里,这样用串口或者JTAG就能在调试过程中动态控制复位时序,而不用每次都重新编译一个版本。
另一个有用的技巧是在CMAC接收路径上插入统计模块,实时统计接收到的报文总数、CRC错误数、长度错误数、空闲错误数,并通过寄存器读出。实际链路调通后,这些计数器是每天运行状态监控的重要组成部分。只要这些数字持续为0,说明链路是很健康的。
如果你用的是Zynq UltraScale+这一类集成ARM核的器件,还可以直接在Linux环境下用Python或者C去访问光模块的I2C寄存器,配合PetaLinux里封装好的I2C驱动,在系统层面做在线监控。这就是“光模块MCU”的思路,即用MCU/ARM负责模块的管理和状态监控,FPGA负责数据面处理。许多量产设备中,光模块控制功能真的是交给一颗独立MCU或者Zynq里的ARM核来做的,FPGA只管业务数据,两者通过内部寄存器通信,职责划分清晰。
在应用层测试方面,FPGA图像处理业务接入100G光口后,常见的一大问题是小包冲击能力差,因为图像帧长往往很大,而且有周期性。与其拿真实的图像数据去测光口,不如在FPGA内部用一个AXI数据发生器合成“接近图像传输模式”的以太网帧,比如每帧长度9126字节的巨型帧,验证光口在巨型帧和突发流量下不会出现FCS错误。这样既避开了图像处理链路本身的不确定性,又能单独考核光口数据通路。
最后分享两点个人的实操心得
第一点是关于参考时钟的调试。实测下来,如果参考时钟频率有偏差,光模块是link不上的,而这一点在FPGA的IP核配置界面里往往不会直接报错,只会显示“link down”或者频繁翻转。遇到这种问题,第一个动作就是用频谱仪或者示波器量一下FPGA参考时钟输入的频率和抖动,不要再反复改FEC配置和复位逻辑。
第二点是想提醒做FPGA的同行,光模块测试很多问题看起来是“模块问题”,但实际根因经常在FPGA周边的电源、时钟和复位设计。决定业务成功率的往往就是这些基础工程,而不只是GT速率和IP核选型。把基础做扎实了,100G光口测试和调试就能少掉一半以上的坑。