资讯动态

FPGA高速接口SRIO例程详解:从Verilog代码到上板调试

发布时间:2026/10/19 12:20:54 来源:尧图企业网站定制
简介一份基于FPGA的Serial RapidIOSRIO回环传输例程面向具备一定数字逻辑基础、希望掌握高速串行接口开发方法的硬件工程师与FPGA学习者。资源以Verilog硬件描述语言源码为主辅以VHDL等设计文件覆盖SRIO协议栈配置、收发链路、时钟管理和回环校验机制配套的编译、仿真脚本约束文件与综合报告能够帮助读者在工程环境中快速复现、定位并解决问题。压缩包共452个文件约80.63MB除源码外还包含批处理脚本、日志、报告及工程配置文件目录结构清晰便于按功能模块检索。目前已有3097人学习下载。通过这套例程可系统理解SRIO自测试的回环模式学习在FPGA上实现高速串行通信的完整流程以及借鉴工程化的调试与排错思路该例程同时适用于通信、嵌入式、医疗设备与高速数据采集等场景对开展板级互连验证具有直接参考价值。1. 为什么说 SRIO 例程是玩 FPGA 高速接口的必修课如果你做过 FPGA 和 DSP 或者 FPGA 和 FPGA 之间的高速数据交换大概率绕不开 SRIOSerial RapidIO。它不同于 I2C、UART 这类低速总线也不同于以太网那样需要软件协议栈支撑SRIO 在硬件层面就把传输、拆包、路由做掉了延迟低、效率高在雷达信号处理、图像采集、软件无线电这些场景里几乎是标配。刚接触 SRIO 的开发者面对那一堆 IP 核配置界面和保护带keepout规则往往一脸茫然手里又没有一份能直接跑起来的参考工程。这份基于 Verilog 的 FPGA SRIO 例程正好补上这块短板——它把一个可综合、可仿真的完整链路拆开摆在你面前从 IP 核参数到数据收发逻辑再到中断上报照着拉一遍你就能在自己的板卡上做出第一条像样的 SRIO 通路。适合想做高速传输但缺参考实现的软硬件工程师也适合给学生做毕业设计打底。2. 先看懂工程结构一个 SRIO 例程该包含哪些部分2.1 目录设计与文件分工拿到例程先别急着打开 Vivado花十分钟把目录结构过一遍。合格的 SRIO 工程一般会分成三个大块IP 核生成脚本、RTL 源码、仿真与约束。这份工程就是按这个套路组织的srio_example/ ├── ip/ # IP 核定义与生成脚本 │ ├── srio_gen2.xci # SRIO IP 核配置文件 │ └── clk_gen.xci # 时钟生成 IP供参考时钟与用户时钟使用 ├── rtl/ │ ├── top.v # 顶层例化 IP 核与逻辑模块 │ ├── srio_init.v # 链路初始化状态机 │ ├── srio_write_path.v # 写请求与写数据产生逻辑 │ ├── srio_read_path.v # 读请求产生与读响应接收 │ ├── srio_doorbell.v # Doorbell 中断接收与上报 │ ├── axis_register_slice.v # AXI-Stream 寄存打拍缓解时序 │ └── srio_common.v # 公共宏定义与常量 ├── sim/ │ ├── tb_top.sv # 顶层测试平台 │ └── rapidio_gen2_model/ # 端点模型用于环路仿真 ├── constraint/ │ └── top.xdc # 引脚与时钟约束 └── scripts/ ├── create_project.tcl # 一键创建工程脚本 └── run_sim.tcl # 跑仿真脚本文件不算多但每份都有明确分工。srio_gen2.xci是核心里面记录了链路宽度、速率、参考时钟频率这些关键参数srio_init.v负责任务完成前的链路训练发送和接收路径被拆成独立文件方便单独仿真和二次修改。仿真目录里的端点模型是 ISE/Vivado 自带的用来做无硬件环境的环路验证。2.2 顶层互联从用户逻辑到 SRIO IP 核顶层top.v做的事很纯粹把用户侧产生的 AXI-Stream 数据通过 SRIO IP 核的 Initiator/Target 接口发出去同时把远端写过来的数据通过 Target 接口收进来。整体连接关系可以描述成下面的样子用户逻辑 (write_path) │ AXI-Stream (tvalid/tready/tlast) ▼ SRIO IP 核的 Initiator 接口 │ RapidIO 串行链路 (4x 5.0 Gbps) ▼ 远端设备 (DSP / FPGA / SRIO 交换机) 用户逻辑 (read_path) ▲ AXI-Stream 读响应返回 │ SRIO IP 核的 Target 接口在实际代码里top.v模块例化时最需要注意的是两个接口的握手信号。SRIO IP 核的 Initiator 发送接口s_axis_wwr_tready信号由 IP 核拉低表示反压用户逻辑必须在tready为高且tvalid为高时才认为数据被接收。很多第一次写 SRIO 逻辑的开发者习惯性把tvalid一直拉高结果遇到反压时丢数据这属于典型的中断时序问题。例程里write_path的做法是内部维护一个 FIFO只有 FIFO 非空才拉高tvalid同时每个时钟周期检查tready两者都为高才弹出 FIFO保证一拍不多、一拍不少。2.3 关键参数速查IP 核配置背后的含义IP 核配置界面有一堆选项真正决定工程能否正常跑通的是下面这组参数。以 4x 链路、线速率 5.0 Gbps 的典型配置为例配置项推荐值说明Protocol ModeRapidIO Gen2选择 Gen2 才能跑到 5.0 Gbps 以上Link Width4x多数板卡走 4x2x/1x 用于兼容Line Rate5.0 Gbps需要确认参考时钟与 GT 引脚分配Reference Clock125 MHzGen2 模式常用 125 MHz需与板卡晶振匹配Logical LayerInitiator Target例程同时支持主动写和被动收Register Space0x30 / 0x34 / 0x38 / 0x3CDoorbell 事件 ID 与中断状态寄存器参考时钟这个坑最多。某些板卡上用的是 156.25 MHz 的差分晶振如果你在 IP 核里配了 125 MHz链路训练会一直失败port_initialized信号永远拉不高。例程里把clk_gen独立出来目的就是让参考时钟和用户逻辑时钟分开管理调试时先确认gt_refclk频率再去看链路状态。3. 核心代码路径:一个请求从进入到返回的完整链路3.1 写事务从 AXI-Stream 到 RapidIO 包写路径是 SRIO 用得最多的功能对端设备发来一包数据FPGA 要把它收下来放进 DDR 或者 BRAM。例程里的srio_write_path.v展示了从 AXI-Stream 接口到 SRIO IP 核 Target 接口的完整处理过程。核心代码不长但值得逐行读// 接收来自 SRIO IP 核 Target 接口的写请求 always (posedge user_clk or negedge rst_n) begin if (!rst_n) begin wr_addr_q d0; wr_data_q d0; wr_be_q d0; wr_req_valid_q 1b0; end else if (m_axis_wwr_tvalid m_axis_wwr_tready) begin // tvalid 与 tready 同时为高说明 IP 核在向用户侧交付写请求 wr_addr_q m_axis_wwr_tdata[63:32]; // 高 32 位是目标地址 wr_data_q m_axis_wwr_tdata[31:0]; // 低 32 位是写数据 wr_be_q m_axis_wwr_tkeep; // 字节使能 wr_req_valid_q 1b1; end else begin wr_req_valid_q 1b0; end end // 向下游 BRAM 发出写请求 always (posedge user_clk) begin if (wr_req_valid_q) begin bram_we wr_be_q; // 写使能直接透传字节使能 bram_addr {wr_addr_q[15:2], 2b00}; // 地址按 4 字节对齐 bram_wdata wr_data_q; end end这段逻辑的核心是理解 SRIO IP 核在 Target 接口上的数据格式m_axis_wwr_tdata的高 32 位是 SRIO 包里的目标地址低 32 位才是真正要写入的数据tkeep信号给出字节使能。例程的做法是先把整包数据暂存一拍再用独立的写信号向下游存储体发出写操作。这样做的目的是把 SRIO IP 核的时序和用户逻辑的解码逻辑彻底隔离开避免因为地址解析不完整导致写错位置。实际调试中用 ILA 抓m_axis_wwr_tvalid和m_axis_wwr_tready可以看到这对信号像是流水线一样交替拉高验证阶段重点关注地址是否按预期递增。3.2 读事务请求发起与响应返回的时序配合读路径比写路径复杂因为它涉及两个方向先由用户逻辑发起读请求再由对端返回读响应。例程里srio_read_path.v处理了这两段逻辑。发起阶段用户逻辑把读地址打包成读请求包发出去接收阶段IP 核把响应包送到s_axis_rxd_tdata接口上。核心代码在响应接收部分// 响应接收注意 tdata 的字段布局 always (posedge user_clk) begin if (s_axis_rxd_tvalid s_axis_rxd_tready) begin // 读响应包第 1 拍完成码在高 8 位事务标签在 [23:16] comp_code s_axis_rxd_tdata[31:24]; if (comp_code 8h00) begin // 完成码为 0表示读成功 read_data_q s_axis_rxd_tdata[15:0]; // 有效数据在低 16 位高位是扩展地址 rd_push 1b1; end else begin // 非 0 完成码按错误处理丢弃数据 rd_push 1b0; err_cnt err_cnt 1b1; end end end读响应包和写请求包的数据格式完全不同写请求的地址和数据各占 32 位读响应的完成码、事务标签和数据挤在同一条总线上。这里的处理方式是先抓完成码如果非零就丢弃并计数上报只有完成码为零才把数据推进下游 FIFO。调试时遇到读数据一直为零的情况别急着怀疑时序先看完成码是不是出现了非零值。如果是多半是目标地址越界或者对端设备根本没有实现读操作。3.3 Doorbell 中断最简单的错误状态上报手段SRIO 协议里Doorbell 是一种特殊的事务类型不携带用户数据只传递一个 16 位的事件 ID。例程里用它做链路状态和错误上报。当对端设备想通知 FPGA 某个事件发生时会发送一个 Doorbell 包IP 核解析后把事件 ID 送往m_axis_dbell_tdata。接收逻辑和中断上报逻辑如下// Doorbell 接收事件 ID 在低 16 位 always (posedge user_clk or negedge rst_n) begin if (!rst_n) begin dbell_event_q 16h0; dbell_pending 1b0; end else if (m_axis_dbell_tvalid) begin dbell_event_q m_axis_dbell_tdata[15:0]; dbell_pending 1b1; // 挂起中断等待软件处理 end else if (dbell_clear) begin dbell_pending 1b0; // 软件写完清除寄存器后拉低 end end这段逻辑的关键在于dbell_clear信号。硬件置位dbell_pending后软件通过寄存器接口读到事件 ID处理完再写清除位。例程里把 Doorbell 事件 ID 的低 6 位用来区分错误类型比如 0x01 表示链路断开0x02 表示读超时0x03 表示写数据包 CRC 错误。对端 DSP 侧的程序只做一件事——根据事件 ID 跳转到对应处理函数。理解了这套机制你在自己的工程里扩展新的错误通知就很简单了。4. 把例程跑起来从 Vivado 建工程到片上调试4.1 一键脚本创建工程并完成综合布线拿到工程后第一步不是手动创建 Vivado 工程而是用仓库里的 Tcl 脚本。很多 SRIO 工程的手动创建过程极其容易出错——IP 核目录找不到、约束文件没加进来、仿真文件类型不对。用脚本可以避免这些问题。打开 Vivado 的 Tcl Console执行cd /your/path/srio_example source ./scripts/create_project.tcl脚本内部做的事按顺序是创建工程、指定器件型号、添加 RTL 和约束文件、生成并例化所有 IP 核。执行完后工程会自动打开。这里要注意工程脚本里默认的器件型号是某 7 系列 FPGA如果你换了型号需要手动修改脚本里的part变量再重新执行。综合前建议先跑一遍report_clock_utilization确认所有时钟频率满足要求。如果出现时序违规常见原因是 SRIO IP 核的 user_clk 频率配得太高把 IP 核里用户时钟频率从 125 MHz 降到 100 MHz 往往能立竿见影。4.2 仿真验证直接在本地跑环路测试如果你的硬件还没准备好可以用仓库里的仿真脚本先验证逻辑正确性。仿真环境的搭建思路是例化一个 RapidIO 端点模型把 FPGA 的发送端接到它的接收端把它的发送端接回 FPGA 的接收端形成回环。这样不需要两块板子互联就能验证完整的收发链路。运行仿真命令cd /your/path/srio_example vivado -mode batch -source ./scripts/run_sim.tcl仿真模型启动后链路初始化状态机开始训练大约经过几百微秒后port_initialized拉高。随后测试平台会发起一笔写事务把 1 KB 数据写入端点模型的内存空间紧接着发起一笔读事务把数据读回来做比对。检查仿真波形时重点看三个信号port_initialized链路是否训练成功、cnt_wr_ok写事务累加器是否增加、cmp_err读回数据比对是否一致。如果cmp_err一直为高问题出在数据字节顺序或者地址对齐上回头检查 3.2 节里wr_addr_q的高低位分离是否匹配。4.3 上板调试用 ILA 抓取关键信号真正上板后仿真通过并不代表硬件没有玄学问题。例程配套的约束文件里已经把 ILA 核需要的调试信号引出来了srio_init_dbg、wr_path_dbg、rd_path_dbg三组信号分别对应链路状态、写路径握手和读路径返回。在 Vivado 里设置 debug 标记后重新综合布线烧录 bit 文件就可以在 ILA 窗口观察。调试 SRIO 的第一条血泪经验是先看port_initialized有没有拉高别急着看数据。如果这个信号一直保持低电平直接检查参考时钟引脚定义、GT 位置约束和链路速率。下面是上板后 ILA 抓到的正常波形应当满足的时序关系时钟周期 1: port_initialized 从低变高 时钟周期 2: 链路握手信号 trencode 开始切换 时钟周期 3: s_axis_wwr_tvalid 拉高tready 跟随拉高 时钟周期 4: 数据开始连续传输每拍一个 64bit 数据如果tvalid拉高了但tready一直为低说明 IP 核内部的反压机制启动了。此时要检查是不是发送队列没及时排空或者链路对端的接收缓冲区满了。这种情况在调试多板互联时尤其频繁处理办法是降低发送速率或者加大 IP 核内部的 FIFO 深度。5. 常见问题与排查记录SRIO 链路联调避坑指南5.1 链路训练失败port_initialized 始终拉不高现象板卡上电后无论怎么复位port_initialized信号一直为低ILA 里看不到链路握手成功。原因多数情况是参考时钟频率不匹配。IP 核里配置的 GT Reference Clock 和板卡实际晶振频率不一致导致收发器无法锁定。另一种可能是 4x 链路里某一条 lane 的引脚约束写错。解决首先用示波器或者 Vivado 里的 IBERT 核测参考时钟频率确认实际值。如果实际是 156.25 MHz 而 IP 里配了 125 MHz改 IP 核参数重新生成。确认时钟无误后检查 XDC 里每个 lane 的引脚位置特别注意差分对的正负不要搞反。5.2 数据能发出去但对端总是收到乱码现象写事务的握手信号正常发送端的tvalid/tready跳变正常但远端设备读到的数据字节顺序错乱。原因这是 SRIO 里最经典的问题——字节顺序不匹配。FPGA 侧默认按照小端序发送 64 位数据而某个远端 DSP 默认按大端序解析导致高 32 位和低 32 位互换。解决在发送路径里把每个 64 位数据拆成两个 32 位交换位置后再拼装。或是在 IP 核配置界面的 Logical Layer 选项卡里调整字节顺序模式。例程里默认是小端序如果对端是大端需要改srio_write_path.v里的数据拼装逻辑。5.3 读事务超时错误计数不断累加现象读请求已发出但读响应迟迟不返回err_cnt寄存器数值持续增加。原因对端设备没有实现读事务功能或者读地址越界。SRIO 规范里如果目标器件收到读请求但无法完成会返回 NAK 或错误完成包。解决用 ILA 抓s_axis_rxd_tvalid如果一直为低说明对端根本没有发响应。换用一个已知能完成读的端点地址测试。如果是地址越界检查读请求包里的地址字段是否落在对端允许访问的区域内。5.4 Doorbell 中断经常丢或者两个事件并成一个现象对端发了两笔 Doorbell但 FPGA 只上报了一个事件或者上报的事件 ID 是两次的合并值。原因Doorbell 包的接收逻辑里没有做多包排队。如果前一个 Doorbell 还没被软件读走第二笔到来时就会把状态覆盖掉。解决在 Doorbell 接收逻辑里加一层极简 FIFO至少缓存 4 个 Doorbell 事件。例程里没加你可以把它作为二次开发的第一步。注意 FIFO 的读指针和写指针位置要单独引出到寄存器方便软件确认是否有堆积。5.5 上板跑一段时间后链路突然断开现象系统正常工作几小时后port_initialized自动拉低之后无法恢复只能整板复位。原因链路不稳定常见原因是电源纹波偏大或散热不佳导致 GT 收发器误码率上升。SRIO 协议在误码超过阈值时会主动断开链路。解决先看板卡电源模块的纹波是否在 50 mV 以内超过就要加强滤波。其次检查 GT 参考时钟的相位噪声如果时钟源质量差误码率会明显升高。软件侧也需要配合上层驱动要捕获port_error中断并做有限次重连不要一断就死等。6. 改造思路把这套 SRIO 例程改成你的业务接口拿到例程最容易上手的二次开发是把写路径从固定地址改成可配置基址加偏移。做法是加一组寄存器软件写入基址后硬件自动累加偏移每次写事务完成后判断是否超过长度上限。我喜欢在写路径里维持一个计数器每收到一笔 AXI-Stream 写请求就递增计数器到达预设值后拉高wr_done中断。上板改过的工程后我会先用逻辑分析仪抓 10 分钟数据确认没有毛刺再对接上层软件联调。从那以后我每次改 SRIO 工程都会强制走一遍三件套先跑环路仿真比对数据再上板抓握手波形核对时序最后做 8 小时连续读写压测。希望这篇笔记能帮你在 SRIO 调通的路上少踩几个坑。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑