新板卡回来,第一件事别急着写业务逻辑,先验证高速串行链路本身能不能跑通。我做过好几个带光纤口的 FPGA 项目,最常用的开板手段就是做 Aurora 回环测试:FPGA 内部发数据,通过光模块发出去,再绕回接收端,比对收发数据是否一致。这一步能把 PCB 布线、GTX 高速收发器配置、参考时钟、光模块选型全部串起来验证,链路干净了,后面做 Aurora 协议通信、上层业务逻辑才有底。
本文以 Xilinx Artix-7 系列 FPGA 为例,使用 Aurora 8B/10B IP核,按照从 IP 配置到上板实测的完整流程,带你搭一个最小可用的光纤回环测试工程。适合刚接触 FPGA 高速接口的开发者和准备调光口但还没想好怎么下手的同学,也适合想快速排查板级链路问题的老手做个参考。
1. 为什么回环测试要用 Aurora,而不是自己写高速收发逻辑
1.1 回环测试到底在验证什么
回环测试这个动作听起来简单,就是把发送和接收短接起来看数据对不对。但放在光纤通信场景里,它验证的层面远比“数据对不对”要多:
- 板级信号完整性:GTX 差分引脚到光模块座之间的 PCB 走线、过孔、阻抗连续性是否正常。
- 光模块与光纤链路:光模块本身是否正常工作,发射和接收光功率是否在合理范围。
- 时钟方案是否正确:GTX 参考时钟是否稳定、频率是否符合配置,用户逻辑时钟是否和 GT 域对齐。
- IP 配置是否有效:Aurora 的复位时序、通道初始化、8B/10B 编解码链路是否正常。
所以,回环测试是板级硬件调试和 FPGA 逻辑调试交汇处的一项工作。直接硬啃复杂协议头前,先把这条最基础的链路打通,能省下大量排查时间。
1.2 Aurora IP核 vs 自研 GT 收发逻辑
很多 FPGA 工程师上手高速接口时,会冒出“自己写一个高速收发逻辑”的念头。我早期也这么干过,但踩了一圈坑之后,结论非常明确:除非公司有专门的 GT 底层专家,否则不要自研。原因很简单:
- GT 高速收发器本质是一个混合信号模块,包含 PLL、CDR、串并转换、8B/10B 编解码、通道绑定、时钟补偿等多个子模块。在 Xilinx 的文档体系里,光 GT 相关的原语和属性就够看很久,全部自己把控的难度和风险极高。
- Aurora 8B/10B 是 Xilinx 提供的免费 IP,官方做好的东西不用,非要自己造轮子,性价比很低。
- 官方 IP 有一整套初始化、复位、错误处理机制,例化后直接给 AXI4-Stream 接口,业务逻辑只面对数据通路,极大降低上层设计复杂度。
1.3 和误码仪、IBERT 方案怎么配合
做回环测试时,有些人会推荐直接用 IBERT,也就是 Xilinx 的集成误码率测试工具。IBERT 也很强,它能直接通过 JTAG 配置 GT 并测试误码率,基本不写 HDL。但 IBERT 测的是物理层,不会经过 Aurora 协议层。实际项目里,光模块的链路和 Aurora 的复位、通道初始化逻辑是紧密耦合的,IBERT 跑通了不代表 Aurora 能正常建立链路。
所以我的习惯是:
- 先用 IBERT 快速筛查硬件问题,确认 GT 的收发通道本身没问题。
- 再用 Aurora 回环工程做协议层验证,确认 IP 配置和用户逻辑无误。
这篇文章讲的就是第二步,也就是 Aurora 回环工程的做法。
2. 配置 Aurora IP核之前,先啃下这四个概念
2.1 8B/10B 编码与线路速率的关系
Aurora 8B/10B 协议基于 8B/10B 编码,每个字节在传输时被编码成 10 bit,因此线路速率 = 有效数据速率 × 1.25。这个 20% 的开销必须从一开始就看清,否则后面计算用户时钟很容易出错。
比如线路速率配置为 5Gbps,那么有效数据速率就是 4Gbps。如果数据位宽选择 32 bit,也就是 4 字节并行,用户时钟频率 = 4Gbps / 32 bit = 125MHz。如果线路速率配的是 6.6Gbps,32 bit 位宽下的用户时钟就是 6.6G / 40 = 165MHz。
实际工程里,我比较喜欢选“5Gbps 线路速率 + 125MHz 参考时钟 + 32 bit 数据位宽”这个组合。原因后面细说,核心是用户时钟恰好也是 125MHz,省了不少跨时钟域处理的麻烦。
2.2 成帧模式与流模式
Aurora 8B/10B IP核支持两种接口模式:Framing 和 Streaming。
Framing 模式把数据组织成帧,帧之间有专门的帧标记,适合需要以帧为单位的通信场景。用户需要处理帧头、帧尾、帧间隔,逻辑相对复杂一些。
Streaming 模式更像一个管道,只要 tvalid 和 tready 握手成功,数据就持续往里灌,没有帧的概念,非常适合回环测试这种纯数据流验证。
做回环测试,我建议直接选 Streaming 模式。它把协议层的复杂性降到最低,我们只需要关心数据通路的正确性。等以后做真正的业务通信,再根据协议需求切换成 Framing 模式不迟。
2.3 通道绑定与多通道概念
Aurora 协议支持把多条 GT 通道绑定成一个逻辑通道,实现更高的数据吞吐量。比如 4 个通道绑定后,128 bit 并行数据宽度。
回环测试通常只需要一个通道,因为我们要验证的最小单元是一个光模块对应一个 GT 通道。先把单通道打通,再加多通道才有意义。否则单通道链路都建立不起来,多通道绑定之后的通道对齐和偏斜补偿会让你更头疼。
2.4 时钟补偿机制
光纤通信两端的参考时钟不可能完全同频,一定存在微小偏差。Aurora 8B/10B 协议通过周期性插入时钟补偿序列来解决这个问题。
理解这一点对调试有实际意义:如果你在抓波形时发现数据流中偶尔穿插了一些看起来不像业务数据的字节,那不是错误,而是时钟补偿序列。在做数据比对时,需要把这段时间的数据屏蔽掉,否则会出现误报。Aurora IP核在用户接口层已经处理了这些细节,但我们要知道它的存在,避免在调试时对着数据流怀疑人生。
3. Aurora 8B/10B IP核配置实操:每个选项都要知道后果
3.1 新建 IP 核与接口模式选择
在 Vivado 的 IP Catalog 里搜索 Aurora 8B/10B,双击打开配置界面。第一页主要选择协议版本和接口模式。
Protocol Version 一般选最新即可,但要注意板卡上另一端的设备是否支持对应版本。回环测试时收发两端都是同一个 IP核,不存在兼容问题,直接选默认版本。
Interface 选择 Streaming,前面已经说过原因。右侧会自动显示数据位宽选项,这一步同时也会显示预估的用户时钟频率,方便你反推线路速率配置。
3.2 线路速率、参考时钟与数据位宽的联动关系
Line Rate 和 Ref Clock 是两个强关联的参数。GTX 的参考时钟频率必须满足其内部 PLL 的分频要求。Xilinx GTX 的参考时钟一般是线速率除以某个整数,常见配置如下:
| 线路速率 | 推荐参考时钟 | 数据位宽 | 用户时钟 |
|---|---|---|---|
| 1.25Gbps | 125MHz | 16 bit | 62.5MHz |
| 2.5Gbps | 125MHz | 16 bit | 125MHz |
| 3.125Gbps | 156.25MHz | 32 bit | 78.125MHz |
| 5Gbps | 125MHz | 32 bit | 125MHz |
| 6.6Gbps | 165MHz | 32 bit | 165MHz |
注意,参考时钟并一定只能用表里推荐的值,但必须满足 GTX 的 PLL 计算规则。如果你不确定,就用 IP 配置界面里的 Validate 按钮检查,Vivado 会直接告诉你这个组合能不能用。
为什么我偏爱 5Gbps + 125MHz 参考时钟 + 32 bit 位宽?
因为这个组合下,用户时钟、参考时钟都是 125MHz。125MHz 是最常见的板载时钟频率,很多开发板出厂就带一个 125MHz 有源晶振,连改板都不用。而且用户逻辑工作在 125MHz,和以太网、DDR、PCIe 等常见 IP 的时钟域天然接近,后续集成不用再绕一大圈跨时钟域。
3.3 GT 参考时钟引脚指定
配置页里会让你选择 GT RefClk 的引脚位置。这里必须查板卡的原理图,看 GT 参考时钟接到了哪个 Bank、哪个引脚。
这里有个常见的坑:GTX 的参考时钟不一定和光模块所在的 Bank 相同,它可能来自专门的参考时钟输入引脚。如果配置错了,IP核综合布线时就会报错,甚至布线成功后链路也起不来。
Artix-7 上常见的做法是光模块放在 Bank 216 或 Bank 217 附近,参考时钟用板载 125MHz 差分晶振连接到对应 Bank 的 GTREFCLK 引脚。具体以你自己的板卡原理图为准,别想当然。
3.4 流控、时钟补偿与 CRC
配置页还有几个次要选项,回环测试阶段建议保持默认或者关闭:
- Flow Control:回环测试不需要流控,关闭即可。
- Clock Compensation:保持使能,这是协议正常工作所必需的。
- CRC:回环测试可以关掉。开了 CRC 会多一些校验逻辑,但流模式下对数据通路没有实质帮助,反而增加出错时排查的复杂度。
- LFSR:IP核内部自带的回环测试模式,后面我会专门提到,但不是我们这次的主线。
3.5 生成 IP核后要关注哪些文件
点击 Generate 之后,Vivado 会输出一堆文件。用不到全部文件,但至少要知道这几个:
- aurora_8b10b.xci:IP核配置文件,重新生成工程时靠它恢复配置。
- aurora_8b10b.v / .vhd:IP核的顶层封装,只暴露 AXI4-Stream 接口和复位时钟等引脚。
- aurora_8b10b_exdes.v:官方示例设计顶层,这是最重要的参考模板,后面我们改工程就从这个文件入手。
- aurora_8b10b_gt_frame.v / aurora_8b10b_gt_stream.v:GT 层的例化,如果要做 GT 层调试,需要打开它看一眼。
4. 回环工程搭建:基于 example design 改出自己的测试逻辑
4.1 为什么不从空白工程开始
每次提到“从 example design 改起”,都会有人问为什么不能自己写一个干净的顶层。原因很简单:Aurora IP核的复位、时钟初始化、GT 例化之间的连接关系非常繁琐,自己写需要花大量时间,而且容易漏掉细节。比如 GT 的 txresetdone 和 rxresetdone 信号必须先稳定,Aurora 的 reset 释放时序才正确,这些知识不在正规文档里翻半天根本发现不了。
所以强烈建议直接从 example design 开始,先把官方工程跑通,再逐步替换成自己的逻辑。这既是最高效的做法,也是官方推荐的做法。
4.2 example design 顶层结构解析
生成 IP核后,在 IP Sources 里找到 example design 文件 aurora_8b10b_exdes.v,展开后大概包含这些模块:
- aurora_8b10b_init:复位和初始化控制,负责产生 reset 信号并等待 GT 复位完成。
- aurora_8b10b_support:核心例化层,包含 GT 实例、时钟模块、状态机。
- 顶层用户逻辑:默认是一个计数器产生递增数据流,把数据发到 TX 接口,同时把 RX 接口的数据接收后通过一个 FIFO 回环到 TX。
example design 默认的逻辑其实已经是一个回环测试了:内部把 RX 收到的数据直接送回到 TX 去发送。但这有一个问题:默认例子里数据是直接从 RX 返回 TX,没有做数据比对和误码统计。我们需要改出一版带有数据比对功能的用户逻辑。
4.3 自写用户逻辑:递增码产生与误码统计
我习惯把 example design 里的用户逻辑整体替换成一个专门写的模块,名字叫 loopback_test_top。这个模块做三件事:
- 产生一个递增的 32 bit 数据序列,作为发送源。
- 将 Aurora RX 接口收到的数据与期望值比对,统计错误字节数。
- 通过 LED 或串口输出链路状态和误码计数。
核心代码如下,你可以直接参考:
module loopback_test_top ( input wire user_clk, input wire user_rstn, input wire channel_up, input wire lane_up, output reg [31:0] s_axi_tx_tdata, output reg s_axi_tx_tvalid, input wire s_axi_tx_tready, input wire [31:0] m_axi_rx_tdata, input wire m_axi_rx_tvalid, input wire m_axi_rx_tlast, output reg [31:0] error_count, output reg [31:0] frame_count, output reg test_done ); reg [31:0] tx_counter; reg [31:0] rx_expect; assign s_axi_tx_tdata = {8'hA5, 8'h5A, tx_counter[15:0]}; // 发送端:持续发送递增数据 always @(posedge user_clk or negedge user_rstn) begin if (!user_rstn) begin tx_counter <= 32'd0; s_axi_tx_tvalid <= 1'b0; end else if (channel_up) begin s_axi_tx_tvalid <= 1'b1; if (s_axi_tx_tvalid && s_axi_tx_tready) begin tx_counter <= tx_counter + 32'd1; end end else begin s_axi_tx_tvalid <= 1'b0; end end // 接收端:与期望值比对 always @(posedge user_clk or negedge user_rstn) begin if (!user_rstn) begin error_count <= 32'd0; frame_count <= 32'd0; rx_expect <= 32'd0; test_done <= 1'b0; end else begin if (m_axi_rx_tvalid && channel_up) begin if (m_axi_rx_tdata[15:0] !== rx_expect[15:0]) begin error_count <= error_count + 32'd1; end frame_count <= frame_count + 32'd1; rx_expect <= rx_expect + 32'd1; end if (error_count > 32'd1000) begin test_done <= 1'b1; end end end endmodule这段代码有几个细节要说明:
- 发送数据前插入了固定字节 A5_5A,这样在调试时能快速从波形上找到数据边界。
- 接收比对只比对了低 16 位,也就是计数器的内容,高 16 位是固定头字节不需要比对。
- error_count 作为只增不减的计数器,超过阈值后拉高 test_done,方便在调试时用逻辑分析仪抓取错误现场。
当然,上面的代码是流模式下的简化版本。如果打开了 tlast 信号,需要额外处理帧边界逻辑,但回环测试中我们用 Streaming 模式,不需要考虑帧边界。
4.4 替换 example design 中的用户逻辑
打开 example design 的顶层文件,找到类似下面这样的代码段:
aurora_8b10b_example_design #( ... ) u_example_design ( ... );把这部分替换成你的 loopback_test_top 实例化,并把 Aurora 的 user_clk、user_rstn、channel_up、lane_up 等信号接到新模块上。注意:
- user_rstn 不要直接用外部复位,建议用 example design 里经过同步处理的复位信号。Aurora 对复位时序要求严格,异步复位可能导致 GT 初始化异常。
- channel_up 拉高前,不要向 TX 接口发送任何数据。一定要把它作为发送使能的条件之一。
4.5 引脚约束:不是随便定的
工程要跑到板子上,必须正确约束引脚。最少需要以下几组:
- 系统时钟:板级 50MHz 或 100MHz,用于 MMCM 产生 GT 参考时钟和用户时钟。
- GT 参考时钟:差分时钟引脚,命名类似 gt_refclk_p、gt_refclk_n。
- SFP 收发差分对:gt_rxp、gt_rxn、gt_txp、gt_txn。
- 板级指示灯引脚:用于观察 channel_up、lane_up 和错误状态。
约束文件里最容易被忽略的是时钟约束。GT 参考时钟和用户时钟虽然理论上同源,但在 Vivado 中必须显式声明它们的时钟关系,否则时序分析根本不过。
一个常用的约束片段:
create_clock -name sys_clk -period 10.000 [get_ports sys_clk_p] create_clock -name gt_refclk -period 8.000 [get_ports gt_refclk_p] set_clock_groups -asynchronous -group [get_clocks sys_clk] -group [get_clocks gt_refclk]这里的 period 根据实际晶振频率调整。125MHz 差分时钟对应 period 8ns。如果还有用户时钟,也应该与 GT 参考时钟之间做好异步时钟域划分,避免跨时钟时序报告满天飞。
5. 上板实测:从加载比特流到看懂链路状态
5.1 连接硬件与加载比特流
回环测试的物理连接相当简单:把一根光纤模块插入 SFP 座,另一端插回同一个模块的 RX 口。没错,就是用一根光纤把模块的 TX 和 RX 短接起来,也叫做外部回环。
如果用的是 SFP 光模块,方向别接反了。光模块的 TX 口是发射端,要用光纤跳线连接到另一个光模块的 RX 口。同一个模块的外回环需要一根专门的光纤跳线把相邻的 TX 和 RX 连起来,或者直接把模块的 TX、RX 用光纤转接头短接。不同光模块类型连接方式有差异,动手前先看模块资料。
接好之后呢,下载比特流。观察以下几个信号:
- lane_up:只要 GT 层完成了初始化,lane_up 就会拉高,说明物理层已经准备好。
- channel_up:在单通道配置下,channel_up 是 Aurora 协议层初始化完成的标志,通常比 lane_up 晚一点。channel_up 拉高后,用户逻辑的 AXI4-Stream 接口才能收发数据。
把这两个信号分别点亮一个 LED,调试时一目了然。
5.2 用近端回环快速拆分问题
上板后如果 channel_up 一直不拉高,别急着怀疑协议,先做一次近端回环测试。所谓近端回环,是指在 GT 内部直接把发送端的串行数据绕回接收端。它不经过光模块和光纤物理链路,只验证 FPGA 内部 GT 通路。
Aurora example design 顶层预留了 LOOPBACK 端口,通常映射为 3 bit 信号,常见的值如下:
| LOOPBACK 值 | 含义 |
|---|---|
| 3'b000 | 正常模式,数据经过光模块 |
| 3'b010 | 近端 PMA 回环,GT 内部收发短接 |
| 3'b100 | 远端 PMA 回环,一般在接收端测试使用 |
把 LOOPBACK 设为 3'b010,也就是近端回环。如果此时 lane_up 能拉高、数据比对无误码,说明 FPGA 的 GT 通路和 Aurora 配置没问题,问题大概率出在光模块或外部物理链路上。反之,如果近端回环都起不来,问题就在 FPGA 内部逻辑配置或硬件本身。
5.3 数据比对的判定标准
近端回环跑通后,切回正常模式做外回环。此时观察误码计数和发送计数。
一个好的回环测试应该满足:
- channel_up 在复位释放后几百毫秒内拉高,并且稳定保持。
- error_count 持续为 0。如果 error_count 缓慢增长或者瞬间飙升,说明链路质量有问题。
- 持续运行 24 小时以上无误码。这个标准虽然保守,但对验证工业级光纤链路来说很有必要。
有些干扰问题并不是立刻出现的。板卡发热后电源纹波变大,或者光纤接头松动造成光功率漂移,都需要长时间运行才能暴露出来。
5.4 用 ILA 抓数据流定位异常位置
如果误码统计不为 0,直接用 ILA 观察 Aurora 的用户接口信号。把 s_axi_tx_tdata、s_axi_tx_tvalid、s_axi_tx_tready、channel_up、error_count 都抓下来。
调试中我遇到过一个很隐蔽的问题:数据错误总是在某一个固定位置出现,看起来像周期性错误。查了好久才发现是 GT 参考时钟的频谱纯净度不够,参考时钟引脚旁边的一颗开关电源频率刚好造成了干扰。后来在 PCB 上给参考时钟引脚加了滤波电容,问题消失。
所以 ILA 抓波形不仅仅要看数据对不对,还要关注错误的分布规律:是均匀散布,还是集中在某个特定区域,这往往对应着不同的问题根源。
6. 实测中常见的坑与排查链路
6.1 channel_up 拉不起来的完整排查链路
这是最经典的问题,没有之一。channel_up 不拉高,说明 Aurora 协议握手没有完成。排查步骤按这个顺序来:
- 确认 GT 参考时钟引脚约束正确。用 ILA 抓 GT 的 txoutclk 或 rxoutclk,如果有时钟输出,说明参考时钟基本正常。
- 确认复位释放时序正确。Aurora IP核要求 GT 的 txresetdone 和 rxresetdone 稳定拉高一段时间后,user_reset 才能释放。example design 里已经处理好了,但如果你自己改了复位逻辑,很容易踩这个坑。
- 检查 lane_up 是否拉高。lane_up 是 GT 层对齐完成的标志,如果 lane_up 都没有,说明问题在物理层或 GT 配置。此时回环类型、参考时钟频率、GTX 电源这些都是怀疑对象。
- 如果 lane_up 正常但 channel_up 不拉高,问题多半在协议层,也就是 Aurora 状态机没有成功握手。常见原因是两端协议版本不一致,或者链路速率不匹配。
6.2 数据比对全错或数据乱序的处理
channel_up 正常,但数据比对大量错误。这不是链路不稳定,而是数据通路有逻辑问题。
先看发送端:发送的数据字节序是否与接收端一致。Aurora IP核里有个配置和 GT 的字节顺序有关,如果发送端和接收端配置的 Endianness 不一样,接收端看到的数据会乱掉。
再看时钟域:接收数据出现在 user_clk 域吗?Aurora IP核的输出接口已经同步到 user_clk 域了,但如果你在用户逻辑里又做了一次跨时钟处理,容易把时序搞乱。
最后看复位释放后的状态:channel_up 拉高之前,RX 数据接口可能来一些无效数据,如果在 channel_up 之前就执行数据比对,会得到一堆假错误。务必在比对逻辑里加入 channel_up 条件。
6.3 偶发误码,但抓不到规律
偶发误码是最难处理的问题,因为它在时间上不可预测。可能跑一小时出现一个错误,也可能一天都正常。这一类问题往往和信号完整性有关,而不是逻辑问题。
可以按以下优先级排查:
- 检查光模块的光功率是否在接收灵敏度范围内。用光功率计测一下,或者在模块诊断寄存器里读光功率寄存器。如果光功率偏低,先清洁光纤接头,再检查光纤弯折半径。
- 确认参考时钟的抖动是否满足要求。GTX 参考时钟对抖动很敏感,如果用普通的时钟发生器,最好用频谱仪看一下相位噪声。如果测试条件不允许,可以尝试更换一个低抖动的时钟源做对照实验。
- 检查板级电源。GTX 收发器对模拟电源的噪声容忍度很低,如果供电纹波偏大,偶发误码几乎是必然的。
这类问题的处理思路是先隔离变量,再逐项排除,不能拿着逻辑分析仪乱抓。
6.4 SFP 模块兼容性问题
用第三方 SFP 模块时,经常遇到和官方模块行为不一致的情况。这不是模块坏了,而是 SFP 模块内部的 CDR、差分摆幅、均衡策略各不相同。
Aurora IP核侧可以通过修改 GT 的 RX 均衡参数来适配不同模块。在 example design 里,GT 的 TXDIFFCTRL、TXPOSTCURSOR、TXPRECURSOR 等参数都是以参数形式暴露出来的。碰到眼图不理想的情况,可以适当调整这些值。
举个例子,有次调试 5Gbps 线路速率,手头的 SFP 模块在 1.25Gbps 下工作正常,升到 5G 后就频繁误码。后来把 TXDIFFCTRL 从默认的 12 调到了 15,同时改了一下预加重参数,误码率降到了 1e-15 以下,问题解决。
7. 回环测试之后的下一步
回环测试通过只代表链路是通的,距离真正的高速通信还差几步。
如果项目接下来要做 Aurora 协议通信,可以把回环逻辑替换为实际的协议模块,把 uart、以太网或者自定义业务数据接到 Aurora 的 AXI4-Stream 接口上。这时要重点验证的就不再是链路本身,而是协议数据的组帧、解帧、以及与业务模块的握手时序。
如果项目要跑更高的速率,比如从 5Gbps 提升到 10Gbps,那就不能继续用 Aurora 8B/10B 了,得换 Aurora 64B/66B 或者更高速率的接口 IP。速率提升后,参考时钟、数据位宽、GT 配置都需要重新评估,回环测试也要重新做一遍。
我个人做项目的习惯是:每一轮板卡改动后,都会先跑回环测试,保留上一轮的测试日志和误码统计,然后把新的测试结果做对比。这样不仅能看到链路是否变差,还能从数据变化趋势里推测出问题出现在哪一次改动上。养成这个习惯之后,调试效率明显提升。
另外多说一句,回环测试的工程文件一定要留存,最好和项目的版本管理放在一起。几个月后如果遇到新问题,翻出当时的回环测试工程重新跑一遍,往往能快速确认是硬件改版导致的问题,还是新逻辑引入的问题。这个习惯救过我很多次。