1. 为什么我劝你先放下官方手册
搞FPGA高速接口的朋友,十个里有八个在Aurora上栽过跟头。UG文档动辄几百页,翻到复位那一章,时序图密密麻麻,信号名一个比一个长,看完之后脑子里只剩一句话:这玩意儿到底从哪一步开始动?我当初第一次配Aurora 64B/66B的时候,光复位就折腾了整整两天,仿真波形跑出来全是红线,ILA抓到的信号跟手册上画的完全对不上。后来才明白,问题不在手册写得不好,而在于手册是给"已经懂的人"看的,它默认你知道哪些信号该先动、哪些信号之间必须保持几个周期的间隔、哪些信号在仿真里看起来是X但在硬件上其实无所谓。
这篇东西就是把我踩过的坑、验证过的流程、以及那张让我豁然开朗的复位时序图,完整地摊开来讲。Vivado 2023.1这个版本在Aurora IP的配置界面上跟2020之前的版本有些差异,尤其是64B/66B模式下新增的几个选项,网上很多老教程直接套用会出问题。我会从IP核的定制界面开始,一个参数一个参数地过,然后重点拆解复位时序——这是整个Aurora链路能不能起来的命门。不管你是刚接触高速收发器的新手,还是之前用过8B/10B想迁移到64B/66B的老手,这篇内容都能让你少走至少一天的弯路。
Aurora 64B/66B本质上是一个轻量级的链路层协议,跑在GT收发器之上,负责把用户数据打包成64位宽、带2位同步头的帧格式,通过高速串行通道发出去。它不像PCIe或者以太网那么重,没有复杂的协商过程,配置对了就能通,配置错了就死给你看。适合用在板间互联、芯片间通信、以及那些需要低延迟高带宽但不需要完整协议栈的场景。你手里如果有两块FPGA板子,或者一块板子上有两个GT Bank,就可以直接拿这个IP做点对点传输实验。
2. Aurora 64B/66B IP核定制界面的关键参数拆解
2.1 从IP Catalog到第一屏配置
打开Vivado 2023.1,在Flow Navigator里点IP Catalog,搜索"Aurora 64B/66B",双击进去。第一屏是Component Name,这个随便起,但建议带上lane数和线速率,比如"aurora_64b66b_4lane_10g",后面调试的时候一眼就能认出来。
接下来是Physical Layer这一栏,几个参数必须想清楚再填:
- Lane Width:可选2、4、8字节。这个决定了GT的内部数据位宽,不是你的用户数据位宽。一般选4字节,对应32位,跟大多数GT的默认配置匹配。选2字节会限制线速率上限,选8字节对时序要求更高,新手不建议碰。
- Line Rate:单位Gbps,填你实际要跑的速率。注意这里填的是线速率,不是有效数据速率。64B/66B编码有3%左右的开销,所以10.3125Gbps的线速率对应大约10Gbps的有效带宽。
- GT Refclk:参考时钟频率,单位MHz。这个必须跟你板子上的实际晶振一致,填错了后面仿真都过不去。常见的有125MHz、156.25MHz、161.1328125MHz。
- Data Decimation:这个参数很多人忽略,但它直接影响你用户接口的时钟频率。如果选1,用户时钟等于线速率除以64再除以lane数;选2就是再除以2。选大了用户逻辑好跑,但延迟增加。
注意:Line Rate和GT Refclk填完之后,Vivado会自动计算PLL的倍频参数。如果算出来的VCO频率超出GT的允许范围,界面会变红。这时候要么调Refclk,要么换Line Rate,没有别的办法。
2.2 64B/66B模式特有的选项
跟8B/10B版本最大的区别就在这里。64B/66B模式下,IP核会多出几个跟齿轮箱和同步头相关的配置:
- Gearbox Mode:有"Sequential"和"Scrambler/Descrambler"两种。Sequential模式延迟低,但需要外部提供同步信号;Scrambler模式自带扰码,链路建立更稳,推荐选这个。
- Sync Header Bypass:一般选false,让IP自己处理同步头。选true的话你得自己维护同步头,除非你有特殊需求,否则别动。
- Little Endian:这个跟你的数据排列有关。如果你的用户数据是低字节在前,选true;高字节在前选false。选错了数据会反着出来,但链路本身能通,调试的时候容易误判。
2.3 复位相关的隐藏参数
在Core Options标签页里,有几个跟复位行为直接相关的选项:
- Simplex/Duplex:全双工还是单工。全双工模式下,复位逻辑要同时管收发两个方向,复杂度翻倍。如果只是单向传输,选Simplex能省不少事。
- Flow Control:选None还是UFC。UFC(User Flow Control)需要额外的复位握手,新手建议先选None,把基本链路跑通再说。
- Vivado Lab Tools:这个选项在2023.1里默认勾选,会生成ILA核。建议勾上,后面调试复位时序的时候,ILA抓波形比仿真直观得多。
配置完这些,点OK生成IP。生成过程大概需要两三分钟,取决于你的机器性能。生成完之后,在Sources窗口里展开IP,能看到几个关键文件:aurora_64b66b_0_core.v是顶层封装,aurora_64b66b_0_support.v里包含了复位状态机,aurora_64b66b_0_gt.v是GT的例化。
3. 复位时序的完整拆解与那张图
3.1 为什么Aurora的复位这么麻烦
Aurora的复位不是一个信号拉高拉低就完事的。它涉及三个层次的复位:GT的复位、IP核内部逻辑的复位、用户接口的复位。这三者之间有严格的先后顺序和时序要求,任何一个环节提前或延后,都会导致链路起不来。
GT的复位由gt_reset信号控制,这个信号必须保持足够长的低电平,让GT内部的PLL锁定、CDR稳定。IP核内部逻辑的复位由reset信号控制,它必须在GT复位完成之后才能释放。用户接口的复位由user_rst或者channel_up信号间接控制,只有当链路建立、channel_up拉高之后,用户逻辑才能开始收发数据。
我见过太多人把这三个复位混在一起,用一个按键同时控制,结果就是GT还没锁好,IP内部逻辑就开始跑,状态机直接卡死。正确的做法是让它们按顺序、分阶段释放。
3.2 复位时序图的逐段解读
下面这张表是我根据实际仿真波形和ILA抓取结果整理的复位时序,比手册上的图更贴近真实情况:
| 阶段 | 信号 | 持续时间 | 说明 |
|---|---|---|---|
| T0 | gt_reset拉低 | 至少100ns | GT进入复位状态,PLL关闭 |
| T1 | gt_reset拉高 | - | GT开始锁定PLL,等待gt_pll_lock |
| T2 | gt_pll_lock拉高 | - | PLL锁定完成,CDR开始工作 |
| T3 | reset拉低 | 至少50ns | IP核内部逻辑复位 |
| T4 | reset拉高 | - | 状态机开始运行,等待channel_up |
| T5 | channel_up拉高 | - | 链路建立,用户逻辑可以开始工作 |
关键点在于T1到T2之间的等待。gt_reset拉高之后,不能立刻拉高reset,必须等gt_pll_lock稳定。这个时间取决于参考时钟频率和GT的配置,一般在微秒级别。如果你用的是125MHz参考时钟,PLL锁定大概需要几十微秒。
提示:在仿真里,
gt_pll_lock可能会在gt_reset拉高后几个周期就拉高,这是行为模型的简化。实际硬件上要慢得多,所以仿真通过了不代表硬件能跑。
3.3 复位状态机的内部逻辑
Aurora IP核内部有一个复位状态机,它根据gt_reset、reset、gt_pll_lock、gt_rxcdrlock等信号的状态,控制整个链路的启动流程。这个状态机的代码在aurora_64b66b_0_support.v里,核心逻辑大概是这样:
always @(posedge clk) begin case (rst_state) IDLE: if (gt_reset) rst_state <= WAIT_PLL; WAIT_PLL: if (gt_p ll_lock) rst_state <= WAIT_CDR; WAIT_CDR: if (gt_rxcdrlock) rst_state <= WAIT_CHANNEL; WAIT_CHANNEL: if (channel_up) rst_state <= READY; default: rst_state <= IDLE; endcase end这个状态机的关键在于,它不会跳过任何一个等待环节。如果你在外部强行拉高reset,状态机可能会进入一个未定义的状态,导致channel_up永远不拉高。
3.4 复位信号的驱动方式
在实际工程里,我一般用一个简单的复位控制器来管理这三个信号:
module aurora_reset_ctrl ( input wire clk, input wire ext_rst_n, input wire gt_pll_lock, input wire gt_rxcdrlock, output reg gt_reset, output reg reset ); reg [15:0] gt_reset_cnt; reg [15:0] reset_cnt; always @(posedge clk or negedge ext_rst_n) begin if (!ext_rst_n) begin gt_reset_cnt <= 16'd0; reset_cnt <= 16'd0; gt_reset <= 1'b1; reset <= 1'b1; end else begin if (gt_reset_cnt < 16'hFFFF) gt_reset_cnt <= gt_reset_cnt + 1'b1; if (gt_reset_cnt == 16'h00FF) gt_reset <= 1'b0; if (gt_reset_cnt == 16'hFFFF) gt_reset <= 1'b1; if (gt_p ll_lock && gt_rxcdrlock) begin if (reset_cnt < 16'hFFFF) reset_cnt <= reset_cnt + 1'b1; if (reset_cnt == 16'h00FF) reset <= 1'b0; if (reset_cnt == 16'hFFFF) reset <= 1'b1; end end end endmodule这个控制器的逻辑是:外部复位按下后,gt_reset先拉低一段时间再拉高;等PLL和CDR都锁定后,reset再拉低一段时间再拉高。两个计数器保证了复位脉冲的宽度足够。
4. 从仿真到上板的完整实操流程
4.1 仿真环境的搭建与波形观察
生成IP之后,Vivado会自动创建一个example design。在Sources窗口里找到aurora_64b66b_0_exdes,右键选择"Open IP Example Design",Vivado会新建一个工程。这个工程里包含了完整的仿真测试平台,直接点"Run Simulation"就能跑。
仿真跑起来之后,重点看这几个信号:
gt_reset:应该在仿真开始后很快拉低,然后拉高。gt_p ll_lock:在gt_reset拉高后一段时间拉高。reset:在gt_p ll_lock拉高后拉低再拉高。channel_up:在reset拉高后一段时间拉高。
如果channel_up一直不拉高,先检查gt_p ll_lock和gt_rxcdrlock有没有拉高。这两个信号是链路建立的前提,它们不拉高,后面全白搭。
注意:仿真里的
gt_rxcdrlock可能需要你手动在测试平台里拉高,因为行为模型不会自动模拟CDR锁定过程。在aurora_64b66b_0_exdes_tb.v里找到gt_rxcdrlock的驱动逻辑,确保它在合适的时间拉高。
4.2 约束文件的编写要点
仿真通过之后,下一步是上板。约束文件里最关键的是时钟约束和GT管脚约束。时钟约束要覆盖gt_refclk、init_clk、以及用户时钟。GT管脚约束要根据你的板子原理图来,包括gtp_txp、gtp_txn、gtp_rxp、gtp_rxn。
# 参考时钟约束 create_clock -period 8.000 -name gt_refclk [get_ports gt_refclk_p] # 用户时钟约束 create_clock -period 6.400 -name user_clk [get_pins aurora_64b66b_0_inst/user_clk] # GT管脚约束 set_property PACKAGE_PIN AB12 [get_ports gtp_txp[0]] set_property PACKAGE_PIN AB11 [get_ports gtp_txn[0]] set_property PACKAGE_PIN AC14 [get_ports gtp_rxp[0]] set_property PACKAGE_PIN AC13 [get_ports gtp_rxn[0]]用户时钟的频率取决于你的线速率和Data Decimation设置。比如线速率10.3125Gbps,4字节位宽,Decimation选1,用户时钟就是10.3125G / 64 = 161.1328125MHz,周期约6.2ns。约束里写6.4ns是留了余量。
4.3 上板调试与ILA抓波形
生成比特流、下载到板子之后,打开Vivado的Hardware Manager,找到你的设备,点"Program Device"。下载完成后,在Hardware Manager里找到ILA核,设置触发条件为channel_up的上升沿,然后点"Run Trigger"。
如果channel_up一直不拉高,按这个顺序排查:
- 先看
gt_p ll_lock有没有拉高。没有的话,检查参考时钟有没有输入,GT的电源和地有没有接好。 - 再看
gt_rxcdrlock有没有拉高。没有的话,检查收发方向有没有接反,或者对端有没有发数据。 - 最后看
reset信号有没有正常释放。如果reset一直拉低,检查复位控制器的逻辑。
我遇到过最坑的一个问题是:板子上的GT参考时钟晶振没有焊接好,导致gt_p ll_lock一直不拉高。换了块板子就好了。所以硬件问题有时候比逻辑问题更隐蔽。
4.4 用户数据的收发测试
channel_up拉高之后,就可以测试用户数据的收发了。Aurora IP核提供了tx_data、tx_valid、rx_data、rx_valid等用户接口信号。一个简单的回环测试逻辑如下:
always @(posedge user_clk) begin if (channel_up) begin tx_data <= rx_data; tx_valid <= rx_valid; end else begin tx_data <= 64'd0; tx_valid <= 1'b0; end end这个逻辑把收到的数据直接发回去,如果链路正常,你会在rx_data上看到跟tx_data一样的数据。注意tx_valid和rx_valid的握手时序,Aurora用的是简单的valid-ready协议,但ready信号由IP内部管理,用户侧只需要管valid。
5. 常见问题速查与避坑经验
5.1 复位相关的典型问题
| 问题现象 | 可能原因 | 解决方法 |
|---|---|---|
channel_up不拉高 | gt_reset和reset同时释放 | 分阶段释放,先等gt_p ll_lock |
channel_up拉高后又拉低 | 参考时钟不稳定 | 检查晶振和时钟约束 |
| 仿真通过但上板失败 | 行为模型简化了PLL锁定过程 | 在硬件上用ILA观察实际锁定时间 |
reset信号一直为低 | 复位控制器逻辑错误 | 检查计数器的复位条件 |
5.2 数据路径上的坑
- 字节序问题:如果收到的数据跟发送的数据字节顺序相反,检查Little Endian选项。这个选项在IP定制界面里,生成之后改不了,只能重新生成IP。
- 数据位宽不匹配:Aurora 64B/66B的用户数据位宽是64位,但如果你选了多lane,实际数据会被拆分到多个lane上。用户侧看到的还是64位,但内部是分lane传输的。
- 时钟域 crossing:用户时钟和GT时钟是不同的时钟域,IP核内部已经做了处理,但用户逻辑里如果有时钟域 crossing,需要自己加同步器。
5.3 我踩过的三个大坑
第一个坑是参考时钟频率填错。我板子上是156.25MHz的晶振,但我填了125MHz,结果Vivado算出来的PLL参数完全不对,gt_p ll_lock死活不拉高。后来用示波器量了晶振输出才发现问题。
第二个坑是复位脉冲太短。我一开始用了一个10ns的复位脉冲,仿真里没问题,但硬件上GT根本来不及响应。后来把脉冲宽度加到100ns以上才稳定。
第三个坑是ILA采样时钟选错。我用用户时钟去采样gt_p ll_lock,结果抓到的信号全是抖动的。后来换成GT的时钟才看到干净的波形。ILA的采样时钟一定要跟被观察信号在同一个时钟域。
5.4 性能优化的几个方向
链路跑通之后,如果想让性能更好,可以调这几个地方:
- 增加Lane数:从1 lane增加到4 lane,带宽直接翻四倍。但要注意GT Bank的布局,不同Bank之间的偏斜会影响链路稳定性。
- 调整Data Decimation:选2可以让用户时钟频率减半,逻辑更容易收敛,但延迟会增加。
- 使用UFC流控:如果接收端处理不过来,UFC可以反压发送端,避免数据丢失。但UFC的复位握手更复杂,建议链路稳定后再加。
6. 关于Aurora 64B/66B的一些个人体会
Aurora这个IP核,说难也难,说简单也简单。难的是复位时序和硬件调试,简单的是它的协议本身很轻量,没有太多花里胡哨的东西。我现在的习惯是,拿到一块新板子,先用Aurora 64B/66B做一个最简单的回环测试,把GT的物理层跑通,然后再往上叠其他逻辑。这样一旦出问题,排查范围就缩小到GT和复位这一块,不会跟用户逻辑混在一起。
另外,Vivado 2023.1的ILA核比之前的版本好用很多,支持更多的触发条件和更深的采样深度。我建议在第一次上板的时候,把gt_p ll_lock、gt_rxcdrlock、reset、channel_up这四个信号都加到ILA里,触发条件设成channel_up的上升沿。这样如果链路起不来,你可以回看触发前的波形,看看是哪个信号先出的问题。
最后分享一个小技巧:如果你用的是多lane配置,每个lane的gt_rxcdrlock是独立的。在ILA里把所有的gt_rxcdrlock都加上,如果有一个lane没锁,channel_up就不会拉高。这时候检查那个lane的收发管脚和参考时钟,问题一般都在那里。