1. 为什么Aurora IP核值得花时间啃下来做FPGA高速接口的兄弟大概都有这种体会板子画好了两颗芯片之间要跑几Gbps的数据选什么协议以太网PCIe还是自己撸一个SerDes收发以太网协议栈太重小数据量场景下开销大得离谱PCIe偏向板内互连跨板走线麻烦自己写SerDes又得处理对齐、时钟校正、错误恢复一大堆事。Aurora就是在这种夹缝里活得很滋润的一类轻量级链路层协议——它不追求通用性专门解决“点对点高速串行传输”这一个问题协议开销小、延迟低、资源占用可控特别适合板间互连、芯片间互连、采集卡到处理卡这类场景。Xilinx从很早的ISE时代就把Aurora做成了IP核Vivado里叫Aurora 8B/10B和Aurora 64B/66B两个版本。8B/10B版本基于GTX/GTH/GTP这些收发器线速率从几百Mbps到十几Gbps都能覆盖编码开销25%逻辑简单调试直观64B/66B版本面向更高速率编码开销降到3%左右但对收发器和参考时钟要求更高。新手入门我一般建议从Aurora 8B/10B开始因为它的用户接口就是AXI4-Stream跟Vivado里大部分IP的接口风格一致调通了之后迁移到64B/66B也不难。这篇内容我打算把Aurora 8B/10B IP核从建工程到上板跑通的完整流程捋一遍包括IP配置里每个参数为什么这么选、AXI4-Stream接口怎么接、复位和时钟怎么处理、上板之后怎么确认链路真的通了。文末会给一个可以直接复用的工程结构包含发送端帧生成、接收端校验、ILA抓波形这几个部分。适合已经会Vivado基本操作、写过一点Verilog但没碰过高速收发器的朋友也适合之前用过Aurora但被复位和时钟搞得头大的老手回顾一下。2. Aurora IP核的核心机制与方案选型2.1 Aurora协议到底解决了什么问题Aurora的本质是一个轻量级链路层协议它把物理层的SerDes收发器包装成一个“像FIFO一样好用”的通道。你往发送接口塞数据对面接收接口就吐数据中间的串并转换、8B/10B编解码、通道绑定、时钟校正、CRC校验全部由IP核搞定。它不像以太网那样有MAC地址、有帧头帧尾、有冲突检测Aurora的帧格式非常精简帧开始SOF、数据、帧结束EOF中间可选插入空闲序列Idle和时钟校正序列CC。这种精简带来的直接好处是延迟极低。我实测过一条4 lane、每lane 3.125Gbps的Aurora链路从发送端写入到接收端读出端到端延迟在百纳秒级别比同速率下走以太网UDP少了将近一个数量级。对于闭环控制、实时采集这类对延迟敏感的应用这个差距是决定性的。另一个好处是资源占用小。Aurora 8B/10B的IP核本身逻辑资源消耗不大主要面积在GT收发器上而GT是硬核不占LUT。我做过一个对比同样跑3.125GbpsAurora方案比用SelectIO自己写源同步接口的方案在时序收敛上轻松得多因为GT的CDR时钟数据恢复把源同步的采样窗口问题彻底解决了。2.2 8B/10B和64B/66B怎么选这是新手最容易纠结的地方。我的经验是看三个指标线速率、参考时钟条件、对编码开销的敏感度。Aurora 8B/10B的线速率范围通常是0.5Gbps到6.6Gbps取决于器件和GT类型编码开销25%也就是说你想跑5Gbps的有效数据线速率得跑到6.25Gbps。它的参考时钟要求相对宽松125MHz或156.25MHz都很常见GT的PLL配置灵活。Aurora 64B/66B的线速率起点更高一般从10Gbps往上编码开销只有3%适合背板互连、数据中心加速卡这类高带宽场景。但它对参考时钟的抖动要求更严通常需要专门的差分时钟源而且调试时如果链路不通排查手段比8B/10B少一些。我个人的建议如果你的线速率在6Gbps以下直接用8B/10B别折腾64B/66B。6Gbps以上再考虑64B/66B而且最好先用Xilinx的IBERT把GT眼图调好再上Aurora协议层。2.3 AXI4-Stream接口的设计哲学Aurora IP核的用户接口是AXI4-Stream这个选择很讲究。AXI4-Stream没有地址概念就是纯粹的流式数据握手靠tvalid和tready。这种接口的好处是你可以用FIFO、用FIFO的IP、用自己写的状态机直接对接不需要像AXI4那样处理地址译码和突发长度。发送侧你把数据放到s_axi_tx_tdata上拉高s_axi_tx_tvalid等IP核拉高s_axi_tx_tready就算握手成功。接收侧反过来IP核拉高m_axi_rx_tvalid你拉高m_axi_rx_tready把数据取走。注意tlast信号它标记一帧的结束Aurora的帧边界就是靠tlast来划分的。这里有个坑Aurora的帧长度不是固定的你可以发一个字节就拉tlast也可以发几千字节再拉tlast。但帧太短会导致协议开销占比高帧太长会增加接收端缓冲压力。我一般建议帧长在64字节到1024字节之间具体看应用。3. Vivado里配置Aurora IP核的实操细节3.1 新建工程与IP核选型打开Vivado新建工程选好器件型号。这里注意Aurora IP核只支持带GT收发器的器件Artix-7里只有部分型号有GTPKintex-7和Virtex-7的GTX/GTH支持更全。如果你用的是Spartan-7或者Artix-7的低端型号可能找不到Aurora IP这不是操作问题是器件不支持。在IP Catalog里搜“Aurora 8B/10B”双击打开配置界面。第一个页面是“Core Options”这里有几个关键选项Lanes通道数1到16。通道数越多总带宽越高但布局布线时GT的物理位置要选对。Vivado会自动帮你分配GT位置但如果你板子上的GT走线是固定的需要在“GT Selection”里手动指定。Line Rate线速率单位Gbps。这个值要跟你的参考时钟和GT的PLL配置匹配。比如参考时钟125MHz线速率3.125GbpsGT的PLL倍频系数就是25倍。Data Path Width用户接口位宽2字节或4字节。位宽越大用户时钟频率越低但逻辑资源消耗略增。我一般选4字节用户时钟频率是线速率的1/208B/10B编码后是1/10再除以位宽4字节就是1/40这里要算清楚线速率3.125Gbps8B/10B编码后有效数据率2.5Gbps位宽4字节32bit用户时钟2.5G/3278.125MHz。如果位宽2字节用户时钟就是156.25MHz。选4字节可以让用户时钟低一些时序好收敛。3.2 时钟与复位配置的避坑要点第二个页面“GT Selection”里参考时钟频率要填对。这个频率是你板子上实际供给GT的差分时钟频率常见的有125MHz、156.25MHz、100MHz。填错了IP核会报错或者上板后链路起不来。“Clocking”页面里有个选项叫“Include Aurora 8B/10B Shared Logic in Core”还是“in Example Design”。如果你只有一个Aurora IP核选“in Core”就行共享逻辑放在IP核内部。如果你有多个Aurora IP核要共享GT的PLL和复位逻辑选“in Example Design”然后把共享逻辑例化一次多个IP核共用。复位这块是新手最容易翻车的地方。Aurora IP核有两个复位输入reset和gt_reset。gt_reset是给GT收发器的reset是给协议逻辑的。这两个复位的时序要求很严格gt_reset必须比reset先释放而且gt_reset释放后要等GT的PLL锁定gt_pll_lock拉高才能释放reset。我见过太多人把两个复位接在一起结果链路时通时不通。正确的做法是用一个复位状态机先拉高gt_reset至少100ns然后释放等gt_pll_lock拉高后再等至少100ns释放reset。reset释放后IP核会开始发送空闲序列等收到对面的空闲序列后channel_up拉高链路才算真正建立。3.3 生成IP核与例化工程结构配置完成后点“OK”Vivado会生成IP核。右键IP核选“Open IP Example Design”Vivado会自动生成一个完整的示例工程包含发送端帧生成、接收端校验、ILA调试核。这个示例工程是学习Aurora的最好材料我建议先把这个工程跑通再改成自己的逻辑。示例工程的顶层结构一般是这样的// 顶层模块 module aurora_example_top ( input wire init_clk_p, input wire init_clk_n, input wire gt_refclk_p, input wire gt_refclk_n, // ... 其他端口 ); // 时钟和复位逻辑 // Aurora IP核例化 // 发送端帧生成 // 接收端校验 // ILA调试核 endmodule发送端帧生成模块通常是一个计数器每发一定数量的数据就拉一次tlast。接收端校验模块检查收到的数据是否和发送的一致如果不一致就拉高错误标志。ILA核抓取AXI4-Stream接口上的信号方便上板调试。4. 上板调试与链路建立全流程4.1 引脚约束与时钟约束上板之前约束文件要写对。GT的参考时钟引脚是专用的在XDC里用create_clock约束create_clock -period 8.000 [get_ports gt_refclk_p]8ns对应125MHz。如果你的参考时钟是156.25MHz周期就是6.4ns。GT的收发差分对引脚也要约束但通常Vivado会自动识别不需要手动写。如果你用的是自己画的板子GT的极性可能接反了这时候可以在IP核配置里勾选“Polarity Inversion”来纠正。用户时钟的约束也要写。Aurora IP核会输出一个user_clk这个时钟的频率取决于线速率和位宽。比如线速率3.125Gbps、位宽4字节user_clk是78.125MHz周期12.8ns。在XDC里约束create_clock -period 12.800 [get_pins aurora_inst/user_clk]4.2 上板后链路不通的排查顺序板子烧进去之后第一件事是看gt_pll_lock和channel_up。如果gt_pll_lock不拉高说明GT的PLL没锁问题出在参考时钟上。检查参考时钟频率是否填对、差分对是否接对、GT的供电是否正常。如果gt_pll_lock拉高了但channel_up不拉高说明GT的PLL锁了但协议层没建立。这时候要检查两个板子的线速率是否一致两个板子的参考时钟频率是否一致gt_reset和reset的时序是否正确光纤或电缆是否插好如果是光模块还要检查光模块的使能信号我遇到过一种情况两块板子单独测试都正常对接起来channel_up就是不起来。后来发现是两块板子的参考时钟来源不同一个来自晶振一个来自时钟芯片频偏太大导致GT的CDR锁不住。换成同源时钟后问题解决。4.3 用ILA抓AXI4-Stream波形链路通了之后用ILA抓发送和接收的AXI4-Stream信号。ILA的采样时钟用user_clk触发条件设成s_axi_tx_tvalid s_axi_tx_tready抓一帧完整的数据。看波形时重点看几个地方s_axi_tx_tvalid和s_axi_tx_tready是否同时拉高握手是否成功s_axi_tx_tlast是否在帧结束时拉高m_axi_rx_tvalid和m_axi_rx_tready的握手情况接收到的数据是否和发送的一致如果发现接收数据有错先检查发送端的tlast是否对齐。Aurora的帧边界靠tlast划分如果tlast位置错了接收端会把两帧拼成一帧或者把一帧拆成两帧。5. 常见问题速查与独家避坑经验5.1 复位时序问题速查表现象可能原因排查方法解决方法channel_up时通时不通gt_reset和reset释放顺序不对用ILA抓两个复位信号和gt_pll_lock先释放gt_reset等gt_pll_lock拉高后再释放resetgt_pll_lock不拉高参考时钟频率填错检查IP配置里的参考时钟频率改成板子实际频率channel_up不拉高两块板子线速率不一致检查两边IP配置统一线速率接收数据错位tlast位置不对用ILA抓发送端tlast确保每帧结束时拉高tlast5.2 时钟域 crossing 的注意事项Aurora IP核的user_clk是IP核内部产生的和你的系统时钟不是同一个时钟域。如果你要把接收到的数据送到系统时钟域处理必须做跨时钟域处理。我一般用异步FIFO深度选512或1024宽度和AXI4-Stream数据位宽一致。这里有个细节Aurora的user_clk频率可能和你的系统时钟频率有微小偏差如果长时间连续传输FIFO会慢慢满或者空。解决办法是让系统时钟和user_clk同源或者用带流控的FIFO快满时拉低m_axi_rx_tready反压。5.3 多lane绑定的布局技巧如果你用多laneVivado会自动分配GT位置但自动分配的结果可能不是最优的。比如4 lane的AuroraVivado可能把lane分配到两个不同的GT Quad里这样会占用更多的GT资源而且布局布线难度增加。手动指定GT位置的方法在IP配置的“GT Selection”页面把“GT Location”从“Auto”改成“Manual”然后指定每个lane的GT位置。一般建议把多lane放在同一个GT Quad里这样共享PLL和参考时钟资源占用最少。5.4 实测经验帧长与延迟的权衡我做过一组实测同样跑3.125Gbps帧长从16字节到4096字节端到端延迟的变化很明显。帧长16字节时延迟约200ns但有效数据率只有理论值的60%左右因为协议开销占比太高。帧长1024字节时延迟约350ns有效数据率能到95%以上。帧长4096字节时延迟约800ns有效数据率97%但接收端FIFO要开得很大。我的建议是如果应用对延迟极度敏感帧长选64到128字节如果对带宽利用率更敏感帧长选512到1024字节。不要走极端16字节和4096字节都有各自的坑。5.5 一个容易被忽略的细节GT的电源滤波GT收发器对电源噪声很敏感。我遇到过一块板子Aurora链路在实验室里跑得好好的到了现场就频繁断链。后来发现是现场电源纹波太大GT的供电引脚上只有普通的电容滤波没有加磁珠和低ESR的钽电容。在GT的供电引脚上并一颗10uF钽电容和一颗0.1uF陶瓷电容后链路稳定了。这个坑在原理图设计阶段就要注意GT的供电通常有专门的电源轨比如1.0V和1.2V这些电源轨的滤波电容要按Xilinx的推荐值来放不要省。6. 工程代码结构与关键模块说明6.1 顶层模块的端口定义module aurora_top ( // 系统时钟和复位 input wire sys_clk, input wire sys_rst_n, // GT参考时钟 input wire gt_refclk_p, input wire gt_refclk_n, // GT收发差分对 input wire rxp, input wire rxn, output wire txp, output wire txn, // 状态指示 output wire channel_up, output wire lane_up, output wire gt_pll_lock );这个顶层模块把Aurora IP核、复位状态机、发送帧生成、接收校验都包进来。channel_up和lane_up引到LED上上板后一眼就能看出链路状态。6.2 复位状态机的实现// 复位状态机 localparam IDLE 2d0; localparam GT_RESET 2d1; localparam WAIT_PLL 2d2; localparam RELEASE 2d3; reg [1:0] state; reg gt_reset_r; reg reset_r; reg [15:0] cnt; always (posedge init_clk) begin if (!sys_rst_n) begin state IDLE; gt_reset_r 1b1; reset_r 1b1; cnt 16d0; end else begin case (state) IDLE: begin gt_reset_r 1b1; reset_r 1b1; cnt 16d0; state GT_RESET; end GT_RESET: begin if (cnt 16d100) begin gt_reset_r 1b0; cnt 16d0; state WAIT_PLL; end else begin cnt cnt 1b1; end end WAIT_PLL: begin if (gt_pll_lock cnt 16d100) begin reset_r 1b0; cnt 16d0; state RELEASE; end else begin cnt cnt 1b1; end end RELEASE: begin // 保持复位释放状态 end endcase end end这个状态机的逻辑是先拉高两个复位等100个时钟周期后释放gt_reset然后等gt_pll_lock拉高后再等100个周期释放reset。init_clk可以用系统时钟也可以用Aurora IP核输出的init_clk频率不用太高50MHz到100MHz都行。6.3 发送端帧生成模块// 发送端帧生成 reg [31:0] tx_data; reg tx_valid; reg tx_last; reg [9:0] tx_cnt; always (posedge user_clk) begin if (!reset_r) begin tx_valid 1b0; tx_last 1b0; tx_cnt 10d0; end else begin if (tx_cnt 10d0) begin tx_valid 1b1; tx_data 32h00000000; tx_last 1b0; tx_cnt tx_cnt 1b1; end else if (tx_cnt 10d255) begin tx_data {24h000000, tx_cnt[7:0]}; tx_last 1b1; tx_cnt 10d0; end else begin tx_data {24h000000, tx_cnt[7:0]}; tx_last 1b0; tx_cnt tx_cnt 1b1; end end end这个模块每256个周期发一帧帧长256字节32bit位宽256个周期就是1024字节这里要算清楚32bit4字节256个周期就是1024字节。帧长1024字节符合我前面建议的512到1024字节范围。每帧的数据是递增的计数器方便接收端校验。6.4 接收端校验模块// 接收端校验 reg [31:0] rx_data; reg rx_valid; reg rx_last; reg [9:0] rx_cnt; reg rx_error; always (posedge user_clk) begin if (!reset_r) begin rx_error 1b0; rx_cnt 10d0; end else begin if (rx_valid rx_ready) begin if (rx_last) begin rx_cnt 10d0; end else begin rx_cnt rx_cnt 1b1; end if (rx_data[7:0] ! rx_cnt[7:0]) begin rx_error 1b1; end end end end接收端校验模块检查收到的数据是否和发送端的计数器一致。如果一致rx_error保持低电平如果不一致rx_error拉高可以用LED指示。7. 从Aurora 8B/10B迁移到64B/66B的注意事项如果你已经跑通了8B/10B想升级到64B/66B有几个地方要改。首先是参考时钟64B/66B通常需要更高频率的参考时钟比如156.25MHz或312.5MHz而且对抖动要求更严。其次是GT的配置64B/66B用的编码方式不同GT的PLL配置和8B/10B不一样Vivado会自动处理但你要确保参考时钟频率填对。用户接口还是AXI4-Stream但位宽可能不同。64B/66B的位宽通常是8字节或16字节用户时钟频率更低。帧格式也有变化64B/66B的帧头是2bit的同步头后面跟64bit数据帧边界靠同步头识别不像8B/10B那样靠tlast。调试手段上64B/66B比8B/10B少一些。8B/10B可以用IBERT看眼图64B/66B也可以用IBERT但协议层的调试信息少一些。我建议先用IBERT把GT的眼图调好确保物理层没问题再上Aurora协议层。8. 我个人在实际操作中的几点体会Aurora这个IP核说难不难说简单也不简单。它的难点不在逻辑设计上而在时钟、复位、GT配置这些“非逻辑”的地方。我见过太多人逻辑写得漂漂亮亮结果上板链路起不来最后发现是参考时钟频率填错了或者复位时序不对。我的经验是先把Xilinx的Example Design跑通不要一上来就改。Example Design里包含了正确的复位时序、时钟约束、ILA配置你把这些搞明白了再改成自己的逻辑。改的时候一次只改一个地方改完就上板验证不要一次改一堆出了问题都不知道是哪个改动导致的。还有一个建议ILA核一定要加而且要把gt_pll_lock、channel_up、lane_up、s_axi_tx_tvalid、s_axi_tx_tready、m_axi_rx_tvalid、m_axi_rx_tready这些信号都抓上。上板后先看这些信号的状态比盲目猜问题快得多。最后说一个我踩过的坑Aurora IP核的user_clk在链路没建立的时候是不稳定的如果你用user_clk去驱动复位状态机可能会死锁。正确的做法是用一个独立的init_clk比如系统时钟来驱动复位状态机等channel_up拉高后再切到user_clk。这个细节在Xilinx的文档里写得很隐晦但实际调试中很关键。