ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

ZYNQ双板Aurora 8b/10b光口通信实战:从IP配置到性能优化

ZYNQ双板Aurora 8b/10b光口通信实战:从IP配置到性能优化 1. 项目缘起与整体方案设计两块ZYNQ板子之间用光口互传数据这个需求在工业采集、多板卡级联、雷达信号处理这类场景里非常常见。我这次拿到的任务很直接两块ZYNQ-7020板子各自挂一个SFP光模块通过光纤对接实现双向数据互传速率跑在3.125Gbps用Aurora 8b/10b协议。选Aurora而不是自己撸一个SerDes收发逻辑原因很简单——Xilinx已经把GT收发器的物理层、通道绑定、时钟校正、CRC校验这些脏活累活全封装好了你只需要关心用户接口的AXI4-Stream握手就行。Aurora 8b/10b这个IP核本质上是一个链路层协议引擎。它把GT收发器的串行差分信号转换成并行数据做8b/10b编解码管理通道初始化握手处理时钟补偿序列还支持帧模式和流模式两种数据传输方式。我这次用的是帧模式Framing Mode因为需要传输不定长数据包帧模式自带CRC校验和帧边界标识接收端能直接判断一帧数据是否完整。整体架构上每块ZYNQ里例化一个Aurora 8b/10b IP核配置成Full Duplex、Framing Mode、2字节数据位宽对应3.125Gbps线速率。用户逻辑这边用一个简单的发送状态机从BRAM里读数据打包成AXI4-Stream帧发出去接收端把Aurora输出的AXI4-Stream数据写入另一个BRAMPL通过AXI-Lite寄存器通知PS读取。PS端跑一个裸机程序负责初始化、配置寄存器、搬运数据。为什么选2字节位宽而不是4字节因为3.125Gbps线速率下8b/10b编码后有效数据率是2.5Gbps2字节位宽对应125MHz的用户时钟时序压力小布线容易收敛。4字节位宽虽然吞吐更高但用户时钟要跑到62.5MHz的双沿或者125MHz的DDR对PL逻辑的时序要求更苛刻。对于大多数中低速采集场景2字节位宽完全够用。注意Aurora IP的线速率和参考时钟必须匹配。3.125Gbps对应125MHz参考时钟如果你板子上是156.25MHz的差分晶振需要在IP配置里选对应的线速率档位否则GT根本起不来。2. Aurora 8b/10b IP核配置详解与避坑指南2.1 IP核关键参数逐项拆解打开Vivado的IP Catalog搜Aurora 8B/10B双击进去。第一页是Physical Layer配置这里有几个参数必须和硬件严格对应Lane Width选2 Bytes。这个决定了用户接口的数据位宽也影响GT的内部数据路径宽度。Line Rate填3.125 Gbps。这个值必须和你的参考时钟频率成整数倍关系否则PLL锁不住。GT Refclk选125 MHz。这是GT收发器的参考时钟输入来自板子上的差分晶振。INIT Clk选50 MHz。这是Aurora内部逻辑的初始化时钟可以用PL的普通时钟。Data Decoding选8B/10B。这个不用改Aurora 8b/10b只支持这一种。Interface选Framing。流模式没有帧边界接收端不知道一包数据从哪开始到哪结束不适合我的场景。Flow Control选None。我不需要背压发送端只管发接收端FIFO深度够大就行。Little Endian勾上。ZYNQ是小端架构数据字节序要一致。第二页是GT Selection这里要选具体的GT Quad和Channel。ZYNQ-7020的PL里通常有4个GTX收发器分布在两个Quad里。你得根据原理图确认SFP光模块连的是哪个GT通道。我板子上SFP接的是GTX Quad 0的Channel 0所以这里选Quad 0、Channel 0。第三页是Core Options这里有几个容易踩坑的地方Column Used选Left或Right取决于你的GT在芯片的哪一侧。选错了布局布线会报错。DRP Mode选AXI4-Lite。这样PS可以通过AXI总线动态改GT参数调试时很方便。Vivado Lab Tools不勾。我们不需要ILA在线调试GT内部信号省点资源。配置完点OKIP核就例化好了。这时候你会看到Aurora IP对外暴露了几组接口用户收发AXI4-Stream、GT差分对、参考时钟输入、初始化时钟、复位、以及一堆状态信号。2.2 复位逻辑与时钟域处理Aurora IP的复位逻辑是新手最容易翻车的地方。它有三个复位相关信号gt_reset、reset、power_down。这三个信号的时序关系必须严格遵守数据手册。gt_reset是给GT收发器的硬复位必须保持至少6个init_clk周期。reset是给Aurora逻辑的软复位必须在gt_reset释放之后、GT时钟稳定之后再释放。power_down正常工作时拉低低功耗模式才拉高。我实测下来最稳妥的复位顺序是这样的// 复位状态机 localparam IDLE 0, GT_RST 1, WAIT_LOCK 2, LOGIC_RST 3, READY 4; reg [2:0] rst_state IDLE; reg gt_reset_r 1b1; reg reset_r 1b1; reg power_down_r 1b0; always (posedge init_clk) begin case(rst_state) IDLE: begin if (user_reset) begin gt_reset_r 1b1; rst_state GT_RST; end end GT_RST: begin if (cnt_gt_rst 6d63) begin // 保持64个周期 gt_reset_r 1b0; rst_state WAIT_LOCK; end end WAIT_LOCK: begin if (gt_pll_lock !gt_reset_done) begin rst_state LOGIC_RST; end end LOGIC_RST: begin reset_r 1b0; rst_state READY; end READY: begin // 正常工作 end endcase end提示gt_pll_lock信号来自Aurora IP表示GT的PLL已经锁定。如果这个信号一直不拉高先检查参考时钟有没有输入再检查线速率配置对不对。时钟域方面Aurora IP的用户接口时钟user_clk是从GT恢复出来的频率等于线速率除以208b/10b编码开销再除以位宽。3.125Gbps、2字节位宽下user_clk是125MHz。你的发送逻辑和接收逻辑都必须在这个时钟域下工作或者做好跨时钟域处理。3. 发送与接收逻辑的Verilog实现3.1 发送端状态机设计发送端的任务很简单从BRAM里读出一包数据加上帧头通过AXI4-Stream发给Aurora IP。AXI4-Stream的握手协议是tvalid和tready同时为高时数据传输有效。我设计了一个四状态的状态机localparam S_IDLE 0, S_SEND_HEAD 1, S_SEND_DATA 2, S_SEND_TAIL 3; reg [1:0] tx_state S_IDLE; reg [15:0] tx_cnt 0; reg [31:0] pkt_len 0; always (posedge user_clk) begin case(tx_state) S_IDLE: begin if (tx_start) begin pkt_len data_len; tx_state S_SEND_HEAD; end end S_SEND_HEAD: begin if (m_axis_tready) begin m_axis_tdata 16h55AA; // 帧头 m_axis_tvalid 1b1; m_axis_tlast 1b0; tx_state S_SEND_DATA; end end S_SEND_DATA: begin if (m_axis_tready m_axis_tvalid) begin m_axis_tdata bram_data[tx_cnt]; tx_cnt tx_cnt 1; if (tx_cnt pkt_len - 1) begin m_axis_tlast 1b1; tx_state S_SEND_TAIL; end end end S_SEND_TAIL: begin if (m_axis_tready) begin m_axis_tvalid 1b0; m_axis_tlast 1b0; tx_state S_IDLE; end end endcase end这里有个细节m_axis_tlast必须在最后一个数据字的同一拍拉高不能提前也不能延后。Aurora IP靠这个信号判断帧结束然后自动插入CRC和帧尾序列。3.2 接收端数据解析与BRAM写入接收端的状态机更简单因为Aurora IP已经把帧边界解析好了你只需要在tvalid和tready同时为高时把数据写进BRAM。always (posedge user_clk) begin if (s_axis_tvalid s_axis_tready) begin if (s_axis_tlast) begin // 一帧结束产生中断通知PS rx_done 1b1; rx_len rx_cnt; end else begin bram[wr_addr] s_axis_tdata; wr_addr wr_addr 1; rx_cnt rx_cnt 1; end end end注意BRAM的写地址必须在帧开始时清零否则第二帧数据会覆盖到第一帧后面。我一开始忘了清零结果PS读到的数据长度一直累加查了半天才发现是地址没复位。3.3 中断与PS-PL交互PL收到一帧数据后通过AXI-Lite寄存器产生一个中断给PS。PS端在裸机程序里注册中断服务函数收到中断后从BRAM里把数据读走。AXI-Lite寄存器映射我定义了四个寄存器偏移名称功能0x00CTRLbit0: 发送启动bit1: 接收使能0x04STATUSbit0: 发送完成bit1: 接收完成0x08TX_LEN发送数据长度0x0CRX_LEN接收数据长度PS端的中断服务函数大概长这样void aurora_isr(void *CallbackRef) { u32 status Xil_In32(AURORA_BASE 0x04); if (status 0x02) { u32 len Xil_In32(AURORA_BASE 0x0C); // 从BRAM读数据 for (int i 0; i len; i) { rx_buf[i] Xil_In32(BRAM_BASE i * 4); } // 清中断 Xil_Out32(AURORA_BASE 0x04, 0x02); } }4. 板级调试与常见问题排查实录4.1 GT收发器不锁定的排查思路两块板子上电、加载bit流之后第一件事是看gt_pll_lock和channel_up信号。如果gt_pll_lock不拉高说明GT的PLL没锁住问题一定出在参考时钟或线速率配置上。我遇到过两种情况一是参考时钟晶振没起振用示波器量差分对没有波形二是线速率填错了3.125Gbps填成了3.125MHz差了1000倍。排查顺序是先用示波器确认参考时钟频率和幅度再核对IP配置里的线速率和参考时钟是否匹配。如果gt_pll_lock拉高了但channel_up不拉高说明GT的物理层通了但Aurora的通道初始化没完成。这时候要检查光纤是不是接反了——TX接RX、RX接TX这是最基本的。另外两块板子的Aurora IP配置必须完全一致线速率、位宽、参考时钟任何一个不一样都握不上手。4.2 数据错位的典型原因数据能收到但内容不对最常见的原因是字节序问题。Aurora IP默认是小端模式但如果你在IP配置里没勾Little Endian或者PS端读BRAM时按大端解析数据就会错位。另一个原因是帧头对齐。我在发送端插入了16h55AA作为帧头接收端如果直接从第一个数据字开始存BRAM帧头也会被存进去。PS读数据时要从第二个字开始读或者接收端状态机跳过帧头。还有一种情况是时钟补偿序列被误当成数据。Aurora在空闲时会发送时钟补偿序列接收端如果没正确过滤这些序列会混进BRAM。不过Aurora IP内部已经处理了时钟补偿用户接口只会输出有效数据所以这个问题一般不会出现。4.3 常见问题速查表现象可能原因排查方法gt_pll_lock不拉高参考时钟缺失或频率错误示波器量差分时钟核对IP配置channel_up不拉高光纤接反或两端配置不一致交换TX/RX核对两端IP参数数据错位字节序不一致检查Little Endian配置和PS解析方式接收数据长度累加BRAM写地址未复位在帧开始时清零写地址中断不触发AXI-Lite寄存器未使能检查CTRL寄存器bit1和GIC配置误码率高光纤质量差或GT预加重不足换光纤调GT的TX Diff Swing提示调试Aurora时Vivado的ILA是神器。把gt_pll_lock、channel_up、m_axis_tvalid、m_axis_tready、s_axis_tvalid、s_axis_tready这几个信号抓出来一眼就能看出问题出在哪个环节。5. 性能实测与优化经验5.1 实测吞吐与延迟两块ZYNQ板子通过1米光纤对接Aurora跑3.125Gbps2字节位宽用户时钟125MHz。实测单向吞吐能跑到250MB/s左右接近理论值312.5MB/s的80%。瓶颈在BRAM的读写带宽和PS端的中断响应延迟。延迟方面从发送端写入BRAM到接收端PS读出数据端到端延迟大约15微秒。其中GT传输延迟约1微秒PL逻辑处理约2微秒PS中断响应约10微秒。如果对延迟敏感可以把PS中断改成轮询能降到5微秒以内。5.2 提升吞吐的几种手段如果250MB/s不够用有几个方向可以优化加大位宽把Lane Width从2字节改成4字节用户时钟不变的情况下吞吐翻倍。但时序收敛难度增加需要仔细约束。提高线速率从3.125Gbps提到6.25Gbps参考时钟也要相应提高到250MHz。ZYNQ-7020的GTX支持到6.6Gbps但PCB走线质量要够好。用DMA搬运PS端用DMA从BRAM搬数据到DDR比CPU逐字读取快得多。ZYNQ的DMAC支持AXI4-Stream到Memory的搬运配置好描述符就行。流模式替代帧模式流模式没有CRC和帧边界开销有效数据率更高但需要你自己管理数据包边界。5.3 长时间运行的稳定性我让这套系统连续跑了72小时每秒钟发一包1KB的数据总共发了约2.6亿包。误码率为零没有出现通道断开或数据错乱。关键措施是GT的预加重和均衡参数用默认值没有乱调。光纤用工业级单模光纤接头用LC/PC打磨过的。电源用低纹波的LDO给GT供电开关电源的纹波会耦合到GT的模拟电路里。板子放在恒温箱里温度控制在25±5℃。注意GT收发器对电源纹波非常敏感。如果误码率偏高先用示波器量一下GT电源的纹波超过50mVpp就要考虑换LDO或加滤波电容。6. 工程文件组织与复现建议6.1 Vivado工程目录结构我的工程目录是这样组织的aurora_loopback/ ├── src/ │ ├── rtl/ │ │ ├── aurora_top.v │ │ ├── tx_fsm.v │ │ ├── rx_fsm.v │ │ └── axi_lite_reg.v │ ├── ip/ │ │ └── aurora_8b10b_0/ │ └── constr/ │ └── aurora_pins.xdc ├── sdk/ │ └── aurora_test/ │ └── src/ │ └── main.c └── scripts/ └── build.tcl约束文件里最关键的是GT差分对的引脚约束和参考时钟约束# GT差分对 set_property PACKAGE_PIN F6 [get_ports gt_txp_out] set_property PACKAGE_PIN E6 [get_ports gt_txn_out] set_property PACKAGE_PIN D6 [get_ports gt_rxp_in] set_property PACKAGE_PIN C6 [get_ports gt_rxn_in] # 参考时钟 set_property PACKAGE_PIN F10 [get_ports gt_refclk_p] set_property PACKAGE_PIN E10 [get_ports gt_refclk_n] create_clock -period 8.000 [get_ports gt_refclk_p]6.2 复现步骤清单如果你想复现这套系统按这个顺序来在Vivado里新建工程选对ZYNQ型号。例化Aurora 8B/10B IP按第2节的参数配置。写发送和接收状态机例化BRAM和AXI-Lite寄存器。写约束文件绑定GT引脚和参考时钟。综合、实现、生成bit流。导出硬件到SDK写裸机程序。两块板子都加载bit流光纤对接跑测试程序。提示两块板子的bit流可以完全一样Aurora IP会自动协商主从。不需要一块配成Master一块配成SlaveIP内部有自动协商机制。6.3 后续扩展方向这套框架搭好之后可以往几个方向扩展一是加一个DDR缓存把接收到的数据先存DDR再慢慢处理二是把Aurora封装成自定义IP用户接口改成AXI4-Stream方便集成到更大的系统里三是加一个简单的协议层在帧头里加源地址、目的地址、包序号实现多板卡路由。我个人在实际操作中的体会是Aurora 8b/10b这个IP核本身很稳定大部分问题都出在复位时序、时钟配置和引脚约束上。把这三样搞对了剩下的就是写状态机搬数据的事。第一次调通可能需要两三天后面再复现就是半天的事。
返回列表