资讯动态

SRIO高速通信Verilog例程实战:从链路训练到性能调优

发布时间:2026/10/19 5:22:07 来源:尧图企业网站定制
简介FPGA SRIOSerial RapidIO回环传输例程使用Verilog硬件描述语言编写面向需要实现高速串行通信的FPGA开发者适用于验证SRIO接口链路与协议层正确性的自测试场景。压缩包内为完整Vivado工程及配套脚本共452个文件整体约80.63MB文件类型涵盖工程配置xpr/xdc/xci/dcp、Verilog/VHDL源码v/vhd/vhdl、仿真与综合脚本bat/tcl/sh、调试与约束文件wdb/ltx/log等打开工程并运行批处理脚本即可复现回环测试流程。例程的核心是采用回环机制将发送数据经SRIO接口返回自身接收端通过比对数据来检测链路错误覆盖时钟管理、数据同步、CRC校验和错误处理等SRIO设计关键点。回环测试是常用的链路自检手段有助于快速定位硬件连接和协议配置层面的问题整个工程目录结构清晰便于按模块查阅与二次开发。已有3097人学习下载适合正在学习Verilog FPGA或SRIO通信协议、需要参考完整工程结构和调试方法的开发者借鉴。1. 拆这份 SRIO 例程之前Verilog 工程师要解决的高并发通信问题做 FPGA 开发的人迟早会撞上 SRIO 这个硬茬子。SRIOSerial RapidIO是一种面向嵌入式系统的串行点对点协议常用于 FPGA 与 DSP、处理器之间的高速数据传输。相比并行的 EMIF 或 PCI 总线它在速率、引脚数和多节点扩展上优势明显但代价是链路训练、事务层格式、IP 核调参这些环节动不动就出玄学问题。这份“fpga srio例程”的价值就是给你一套从链路训练到 Verilog 收发事务的完整参考实现适合刚接触 SerDes 级协议、想直接用代码驱动 IP 核接口的开发者也适合那种链路已经调通但吞吐一直上不去的场景。它能帮你把最容易翻车的部分变成可对照、可修改的代码。2. SRIO IP 核选型与配置先搞懂协议分层再谈 Verilog 接口SRIO 不是简单的串行口先看协议栈再动手。我见过有人拿到例程就直奔收发模块结果连端口都没找到原因就是分不清哪些是 IP 核固化的、哪些是需要自己写的。本章先理清边界再讲参数最后看工程骨架。2.1 协议栈与 IP 核的边界哪些黑盒部分可以放心哪些必须自己写SRIO 协议分三层逻辑层Logical Layer、传输层Transport Layer和物理层Physical Layer。逻辑层定义了读写操作、消息和全局维护请求传输层负责路由和设备 ID物理层处理串行链路的训练、加扰和通道对齐。FPGA 厂商的 SRIO IP 核通常把物理层、传输层以及逻辑层的大部分硬件逻辑都封装成黑匣子用户直接面对的是逻辑层事务接口和物理层状态引脚。从 Verilog 的角度看需要关心四类信号一是逻辑层请求接口即发起端口用来发送 NREAD、NWRITE、SWRITE 等事务二是逻辑层响应接口即响应端口用来接收对端返回的读数据和维护响应三是链路状态信号比如端口初始化完成、链路训练完成、错误标志等上板必须观察这几根线四是用户时钟与复位IP 核会输出一个与链路速率相关的高速用户时钟所有逻辑层接口信号都要同步到该时钟下。常见做法是直接用 IP 核生成的 example design 作为框架把其中写好的收发示例跑通后再往里面加业务代码。不要自己从零写训练序列或者扰码逻辑那些是数模混合设计的深水区例程里也根本不会展开。你要动手的实际只有请求发起和响应接收这一层。2.2 关键参数的选择链路宽度、线速率、设备 ID 和参考时钟IP 核配置界面里的参数繁多但真正决定例程能不能在目标板上跑通的主要是下面这几个。参数典型值选择考虑链路宽度1x / 2x / 4x对端设备能力可协商降级线速率2.5 / 3.125 / 5.0 Gbps受 PCB 板材、连接器、参考时钟质量影响设备 ID 宽度8 / 11 / 16 bit取决于是单跳还是大规模交换参考时钟125 / 156.25 MHz必须与 SerDes 的倍频关系严格匹配链路宽度和线速率的乘积决定了理论吞吐上限。比如 4x 5.0Gbps 的理论吞吐约 20Gbps考虑 8b/10b 编码后有效带宽打八折再扣掉事务头开销能跑到 15Gbps 就不错。对端口通信不需要宽链路时2x 往往更稳因为布线宽松串扰小。设备 ID 宽度要和对端一致否则维护请求和路由表会出问题。很多例程默认用 8 位 ID两个节点通信够用。参考时钟是最容易出错的点。SRIO SerDes 对参考时钟的频偏要求很高一般要求在 ±100ppm 以内而且要干净。有的开发板为了兼容多种协议参考时钟由可编程 PLL 产生如果配置不对物理层训练就永远不会完成。我的习惯是先用板子上固定的 OSCH 时钟把链路调通后再换到 PLL 时钟。2.3 生成工程后怎么找例程代码文件清单与端口速览生成 SRIO IP 核后工程里通常包含 IP 例化文件、约束文件、仿真脚本和 example design 源码。example design 目录下常见的文件类别如下文件作用srio_top.v顶层模块例化 IP 核和收发示例逻辑example_tx.v发送事务示例状态机example_rx.v接收事务示例与寄存器展示逻辑srio_ila.v / vio 例化在线调试抓信号用.xdc 约束差分引脚、参考时钟、复位引脚约束顶层里最重要的接口就是逻辑层请求端口。不同厂商的命名不同但总线时序基本都是 AXI4-Stream 风格tvalid、tready、tdata、tuser。tdata是 64 位包含事务头信息和载荷tuser里包含设备 ID、事务标签和头部校验。下面是一段从端口库里截取状态的示例代码。// 读取物理层链路训练状态 wire link_initialized; wire port_initialized; assign link_initialized srio_core.link_initialized; assign port_initialized srio_core.port_initialized; always (posedge core_clk) begin if (~core_rst_n) begin link_state_reg 3b000; end else begin link_state_reg {port_initialized, link_initialized, tx_ready}; end end这段代码的作用是把两个关键状态信号打拍到寄存器里方便上板时用逻辑分析仪直接看。link_initialized是物理层链路训练完成的标志port_initialized是端口确认路由可达的标志两者都为高时才允许发起逻辑层事务。如果只看link_initialized就急着发数据会丢包。从例程中找到这两个信号的过程比读任何手册都快。3. Verilog 收发通路实战从发出事务到收到响应跑通链路训练只是万里长征第一步真正花时间的是把 NWRITE、NREAD 这些事务用状态机发出去再把响应收回来。这一章我会按照“写请求 → 读请求 → 寄存器封装”的递进来拆解。3.1 NWRITE 发送请求接口上的一次事务时序NWRITE 不需要对端响应是 SRIO 里吞吐效率最高的写事务。它的一次事务分头部和载荷两部分头部的关键字段包括事务类型、目标设备 ID、目标地址、长度和优先级。发送侧的状态机控制tvalid与tready的握手。下面列一个简化的 NWRITE 发送状态机。// 简化版 NWRITE 发送状态机 module srio_nwrite_tx #( parameter TID_WIDTH 8 )( input wire clk, input wire rst_n, // SRIO IP 逻辑层请求接口 output reg ireq_tvalid, input wire ireq_tready, output reg [63:0] ireq_tdata, output reg [15:0] ireq_tuser, // 用户控制 input wire tx_start, input wire [31:0] tx_dest_addr, input wire [15:0] tx_dest_id ); localparam IDLE 2d0, SEND_HEADER 2d1, SEND_PAYLOAD 2d2, WAIT_DONE 2d3; reg [1:0] state; reg [3:0] payload_cnt; always (posedge clk or negedge rst_n) begin if (!rst_n) begin state IDLE; ireq_tvalid 1b0; ireq_tdata 64b0; ireq_tuser 16b0; payload_cnt 4b0; end else begin case (state) IDLE: begin if (tx_start) state SEND_HEADER; end SEND_HEADER: begin ireq_tvalid 1b1; // 头字段按 IP 核文档拼接类型 设备ID 地址 长度 ireq_tdata {8h04, tx_dest_id, 8h00, tx_dest_addr, 8h02}; state SEND_PAYLOAD; end SEND_PAYLOAD: begin if (ireq_tvalid ireq_tready) begin ireq_tdata tx_data_buffer[63:0]; payload_cnt payload_cnt 1b1; if (payload_cnt 4d1) begin ireq_tvalid 1b0; state WAIT_DONE; end end end WAIT_DONE: state IDLE; default: state IDLE; endcase end end endmodule这段代码的逻辑是收到用户启动信号后先发 64 位头部然后在tvalid tready同时为高时输出 64 位数据。payload_cnt用来控制载荷长度这里假设只发两个 64 位字。需要特别说明的是ireq_tuser的字段定义高度依赖 IP 核手册比如事务标签存放在哪几位、目标设备 ID 是否需要在tuser中再重复一次不同版本有差异。例程里给的那个生成模板就是最好的参考资料。参数层面的两个坑一是tready可能在整个事务中间拉低状态机必须支持握手等待不能在tready拉低时继续翻转状态二是头部和载荷之间是否有气泡取决于 IP 核配置。某些配置下头部需要独占一拍tvalid在头部阶段之后要主动拉低一个周期不然数据会被吞掉。3.2 NREAD 读请求与响应匹配乱序问题其实不用慌NREAD 请求发出后对端会把数据通过响应端口返回。每个请求都带有事务标签Transaction ID而响应事务中会携带相同的标签所以即使多个读请求并行发出响应乱序回来也能通过标签对齐。例程里通常用一个标签监测模块来维护这个流程。// 响应标签匹配示例 reg [7:0] tid_outstanding; reg [7:0] expected_tid; wire [7:0] resp_tid irsp_tdata[63:56]; always (posedge clk) begin if (ireq_tvalid ireq_tready) begin tid_outstanding tid_outstanding 1b1; end if (irsp_tvalid irsp_tready) begin if (resp_tid expected_tid) begin // 匹配成功锁存响应数据 read_data_valid 1b1; read_data irsp_tdata[31:0]; end end endtid_outstanding统计未完成的读请求数量expected_tid则是下一次希望匹配的标签。真正大型系统里多个请求同时 outstanding需要用 CAM 或 RAM 表把标签和数据总线关联起来。例程里往往只做顺序匹配也就是假设响应按请求顺序返回。在单链路、少量 outstanding 的场景下这种假设成立但如果你把max_outstanding调高就要改造匹配逻辑。多数例程会把响应数据通过寄存器或状态机搬运到本地 RAM。这部分在仿真时最容易出问题的地方不是逻辑本身而是用户时钟和 IP 核时钟不同步。建议把响应接口务生种在同一时钟域也就是直接用 IP 核输出的core_clk。3.3 把收发逻辑封装成寄存器组让 CPU 或软核也能直接操作直接操作 SRIO 的 AXIS 接口对业务研发太不友好例程里常见的做法是加一层寄存器桥。下面是一个简单的 AXI-Lite 从机寄存器映射。地址偏移寄存器名称方向说明0x00CTRLRW启动发送、软复位0x04SRC_IDRW源设备 ID0x08DEST_IDRW目标设备 ID0x0CTX_ADDRRW要发送的目标地址0x10TX_LENRW发送长度字节0x14STATUSRO链路状态、复位状态0x18IRQ_MASKRW中断使能掩码封装的核心思想是让寄存器写操作触发状态机而不是在中断例程里直接搬数据。时序上遵循 AXI-Lite 的标准握手awvalid/awready、wvalid/wready、bvalid/bready三个通道。例程里一般会把这部分命名为srio_regfile.v引入后就不需要频繁修改发送模块。4. 仿真、上板和排坑SRIO 最容易翻车的地方在这五处这一章直接给出我拆 SRIO 例程时积累的排查方法和五条血泪踩坑记录。仿真是第一步上板调试是第二步但真正耗时间的往往是那些看起来“没问题”却通不了链路的诡异情况。4.1 先跑回环仿真确认 IP 核链路训练状态机拿到例程后的第一件事不是在板子上跑而是跑仿真。IP 核生成的 example design 里通常包含一个 testbench里面设置了 PCS Loopback 或 Near-End Loopback 模式。在这个模式下发送数据经过 PCS/PMA 后直接回环到接收端不需要外部硬件连接。仿真步骤# 仿真脚本示例 vsim -c -do vsim -t ps central_router_tb; run -all运行后重点观察三个东西第一link_initialized是否能在仿真时间内拉高第二port_initialized是否拉高第三发起第一个 NWRITE 事务后对端的接收端口能否收到完整数据。仿真的是 IP 核模型不会出现硬件抖动如果仿真都通不过多半是参数配置错而不是硬件问题。注意回环模式分为 PCS Loopback 和 PLL Loopback。PCS Loopback 是数据从 PCS 层绕回物理层训练序列仍然会被接收端处理PLL Loopback 则是模拟信号层面绕过 SerDes两者对链路训练状态的体现完全不同。排查时要确认例程配置的是哪种。4.2 上板后如何用 ILA 和 VIO 观察链路状态仿真通过后上板先不要跑业务逻辑用 ILA集成逻辑分析仪抓物理层状态信号。建议抓这些信号link_initialized、port_initialized、lane rx_error、user_clk和reset_n。再用 VIO 虚拟 IO 控制 IP 核的复位引脚做到按需复位链路。我一般把 ILA 的采样深度设成 16384触发条件设为link_initialized 0或reset_n 0。这样一旦链路掉训练能抓到掉训练前的最后状态。很多例程里自带 ILA 核但如果厂商的例程没有就在顶层手动加一个。4.3 避坑链路训练怎么也到不了线速现象link_initialized始终为低或者链路协商速率停在 2.5Gbps配成 5Gbps 就是起不来。原因这是 SRIO 上板最常见的问题占比大概一半以上。通常出在参考时钟的频偏和抖动超标上。另一个原因是对端的 lane 速率协商能力有限为了兼容低端设备自动降速。解决先用示波器测参考时钟波形确认频率和幅度。然后查 IP 核配置里是否使能了“链路速率自动协商”。如果不需要兼容老设备直接关闭自动降速强制配置速率。通过这两步大多数链路训练问题都能解决。4.4 避坑时序收敛报错核心逻辑放不进去现象综合实现时报出时序违规关键路径是用户逻辑到 SRIO IP 核逻辑层接口的路径。原因用户时钟和 IP 核时钟不是同一个时钟源或者跨时钟域的约束没做好。SRIO IP 核的core_clk频率由线速率决定用户逻辑如果使用另一套时钟路径上会出现异步亚稳态。解决把用户逻辑时钟切换到 IP 核输出的user_clk或者加一个同步时钟域交叉寄存器。此外例程里自带的/* dont_touch */约束不要随便去掉那些是保 IP 核输出端口不被综合器优化掉的关键属性。4.5 避坑吞吐远低于理论值只有配置速率的一半现象连续 NWRITE 写入通过计数器测得的实际吞吐只有理论带宽的 40% 到 60%。原因大概率是事务长度太短。SRIO 的每个事务头部要占用 8 到 16 字节如果每次只写 16 字节头部开销占比太高。另一个原因是用户时钟频率不够导致逻辑层接口未能饱和。解决把单次事务长度改成 256 字节或更大使用 SWRITE 事务减少头部字段。检查用户时钟是否跑到了 IP 核允许的最高频率比如 5.0Gbps 速率下逻辑层时钟通常能到 250MHz 或 312.5MHz如果你的逻辑只能在 125MHz 下工作吞吐自然打七折。5. 性能调优与验证方法用一张检查单确认 SRIO 是否跑在合理区间到这步链路已经稳定但“能通”和“够快”是两回事。最后分享一个我自己一直在用的性能验证技巧。先给吞吐建一个基准公式。假设线速率 5Gbps、链路宽度 4x、使用 SWRITE 事务且每次长度 256 字节理论上限约等于(5Gbps * 4) / 10 * (256 / (256 8))。这里除 10 是 8b/10b 编码开销一个事务只有大约 8 字节头。以此估算理论有效吞吐约 1.85GB/s。如果实测低于这个值的 80%就需要调整参数。我通常用一个计数器来验证发送侧累计发出的字节数接收侧累计收到的字节数再用定时器窗口计算差值。下发一段已知长度的数据后对比收发计数是否相等。如果在长时间跑数据时收发计数差值不断增大说明链路出现了丢包或重传这比看任何状态寄存器都直接。约束检查单用 verify_tbench 跑 1000 个随机事务 1. 配置源端 ID / 目标 ID确认两边一致 2. 发送 64 字节 / 256 字节 / 1024 字节三段数据 3. 计数器统计发送字节计数 接收字节计数 4. 检查 read response 的标签是否有未匹配残留 5. 用时序报告确认用户时钟是否能跑满 250MHz从那以后我每次完成 SRIO 例程调试都会强制走一遍这张检查单——先看链路状态再看收发计数最后查时序报告。三件事都过了才敢说这个链路真的能投产。SRIO 是那种“仿真靠运气、上板靠经验”的接口希望你这次拆例程不用再吃我吃过的亏希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑