简介:面向FPGA开发者的SRIO高速串行通信参考例程,基于Verilog硬件描述语言实现,包含完整的回环自测机制,用于验证SRIO接口的收发链路与协议层正确性,适合学习SRIO协议、FPGA高速接口设计以及嵌入式互连开发的工程师参考。压缩包内共452个文件,约80.63MB,以.v Verilog源码、.xdc约束文件、.dcp综合实现结果、.bit比特流为主,同时包含.vhd/.vhdl设计文件、.tcl/.bat脚本以及docx说明文档,覆盖设计输入、综合实现、仿真调试和板级下载多个环节,目录结构清晰,便于按流程检索。已有3075人学习。通过该例程可以快速搭建SRIO回环测试环境,掌握FPGA中SRIO收发端口设计、时钟管理、CRC校验和错误处理等关键逻辑,回环模式下发送数据经接口返回自身,通过比对一致性能有效定位硬件与协议层故障;同时可参考工程中的仿真脚本与调试文件,用于后续高速串行通信项目的验证和排错。
1. 项目概述与核心诉求
1.1 什么是SRIO,为什么用FPGA做SRIO
SRIO(Serial RapidIO)是一种面向嵌入式系统的高性能串行总线协议,广泛应用于通信基站、雷达信号处理、工业控制和医疗图像等领域。它和PCIe、Ethernet最大的区别在于,SRIO更强调“低延迟、可预测、点对点传输”,特别适合多处理器之间、FPGA与DSP之间的高速数据搬运。我最早接触SRIO是被项目里“把高速AD采集的数据送到TMS320C6678进行算法处理”这个需求逼出来的,当时第一反应是用并行总线,但带宽和布线成本都扛不住,换成SRIO链接,一对差分线就能跑到5 Gbps甚至更高,工程上省了太多事。
FPGA做SRIO的优势在于,SRIO协议栈中的物理层和链路层可以直接用厂商IP核实现,用户只需要关注应用层逻辑。相比DSP或者CPU侧,FPGA能更灵活地组织数据格式、控制时序、处理多通道并发,而且一旦需要调整包长度或者映射地址,改逻辑比换芯片方案快得多。这也是为什么网上搜“fpga srio例程”会有大量需求——大家想看到的不是一个理论说明,而是“怎么在Vivado/Quartus里把IP配好,写出能够实际跑通的参考代码”。
1.2 例程的功能目标与适用人群
一个完整的FPGA SRIO例程,通常要完成三件事:第一,物理链路能够稳定训练成功,端口状态寄存器显示“OK”;第二,能够通过维护包(Maintenance Packet)读取远端设备的寄存器,确认链路两端可以互相访问;第三,能够发起或者响应DirectIO/HELLO包,实现真实的数据读写。
这个例程适合两类人。一类是刚接触高速串行接口的FPGA工程师,可以通过它快速理解SRIO的初始化流程、包结构和时序;另一类是已经在别的协议(比如PCIe、Aurora)上有经验、但需要转做SRIO的人,他们最需要的是“SRIO和PCIe到底差在哪”这种对比性认知。我在下面的内容里会把整套方案的选型、配置、代码结构、调试方法都拆开讲,并且把我在实际项目中踩过的坑列出来,方便大家直接参考。
2. 整体设计方案与技术原理
2.1 SRIO协议栈分层拆解
SRIO协议分三层:物理层(Physical Layer)、传输层(Transport Layer)和逻辑层(Logical Layer)。物理层负责串行编解码、时钟恢复、链路初始化和错误管理,常见速率有1.25/2.5/3.125/5/6.25 Gbps每通道,支持1x、2x、4x等宽度。传输层负责路由寻址,就像快递公司决定包裹从哪个分拣中心走,SRIO通过DeviceID来标识设备。逻辑层定义了包类型和事务格式,例如NREAD、NWRITE、SWRITE、DOORBELL、MESSAGE等。
用生活类比来说,SRIO传输数据就像在高速公路上跑货车。物理层是路基和收费站,负责让货车能安全开上去;传输层是导航系统,决定这辆车该走哪条匝道到哪个出口;逻辑层是货物装箱单,告诉收货人车里装的是什么、是普通快递还是加急件。FPGA例程里我们最常操作的其实是逻辑层,因为IP核已经封装好了下面两层,用户只需要构造包头和Payload,IP核会自动把包序列化发送,然后接收侧再解析出来。
2.2 FPGA侧IP核选型
目前主流FPGA厂商对SRIO都有成熟的IP核支持。Xilinx方面,从7系列到UltraScale+,使用的IP核叫“Serial RapidIO Gen2”(在Vivado里搜索“SRIO”即可找到),支持到Gen2规范,线速率最高6.25 Gbps。Intel/Altera也有类似的RapidIO IP核,但生态相对没那么活跃。国产FPGA比如复旦微、安路等,部分型号已经开始集成SRIO硬核或者提供软核方案,但通用性还比不上Xilinx。
选IP核首先要看目标芯片是否包含高速收发器(GTX/GTH/GTY)。SRIO必须跑在高速收发器上,普通IO不可能实现。我常用的组合是Kintex-7 XC7K325T + Xilinx SRIO IP核,线速率3.125 Gbps,1x模式,这个配置在工业项目里性价比很高。开发工具建议使用Vivado的较新版本,因为旧版本对SRIO IP核的license控制和补丁支持不是很友好。
2.3 例程系统框图与数据流
一个清晰的例程数据流大致如下:用户侧逻辑产生数据包→送到IP核的Transaction接口→IP核完成包封装并发送到GTX收发器→经过外部线缆/背板到达对端设备;接收方向则相反,GTX收到的串行数据经过IP核解码和解析,最终以事务包的形式送到用户逻辑。
例程中通常还会包含一个“维护模块”和“寄存器模块”。维护模块负责发起维护读/写请求,用来读取对端设备的错误状态寄存器,这是判断链路是否健康的重要手段。寄存器模块则用来模拟一个简单的内存空间,接收对端发来的NWRITE数据,或者响应NREAD请求。下图是逻辑层面的模块划分,大家在自己工程里可以直接参考这个结构,避免把用户逻辑和IP核的复杂信号混在一起(我这里用文字描述模块连接关系:user_logic -> srio_ip -> GTX -> SRIO链路,反向同理)。
3. IP核配置与工程实现要点
3.1 Vivado中SRIO IP核创建步骤
我们以Xilinx Vivado为例,演示最典型的创建流程。首先打开Vivado工程,点击IP Catalog,搜索“Serial RapidIO Gen2”,双击打开配置界面。在Basic页面,需要填写Device ID、端口模式、链路宽度和线速率等参数。这些参数必须和对端设备保持一致,否则链路训练阶段就会失败。
关于Device ID有一点需要特别注意:本端设备的ID和自己要维护的地址映射表无关,而是要和对端配置的路由规则相匹配。比如本端device_id=0x00,对端=0x01,如果对端的路由表没有把0x00添加到它的转发表里,那么本端发出的包就会被对端丢弃。很多例程跑不通,就是因为只配置了自己的ID,却忘了对端也需要知道你的ID。
端口模式方面,如果只是和单个DSP通信,选择1x模式就够了;要是做背板交换,可以和交换机芯片连接,此时用4x模式能增加带宽,但PCB布线的难度也会相应增加。线速率建议先选3.125 Gbps验证功能,不要一上来就跑5 Gbps,信号完整性处理不好,问题会非常难排查。
3.2 关键参数配置详解
在IP核的“Link Initialization”选项中,有一些小细节容易忽略,但影响极大。比如“AckID”相关的检查,IP核初始化时会发送带AckID的包,如果双方AckID不一致,链路会反复重传甚至放弃。通常设置成自动协商即可,不要手动写死。
另一个关键是“Port Width”和“Device ID Width”。Device ID宽度支持8位或16位,常见的是8位。端口宽度要和线速率联动,1x模式下默认的参考时钟频率一般是125 MHz(取决于线速率和收发器参考时钟倍频关系)。这些参数如果选错,最常见的现象是“链路Training失败”或者“复位后Status寄存器一直显示0”。
IP核还提供了“Lane0/1/2/3”的极性控制选项。如果你的PCB走线不小心把差分对的P/N反了,不用改板子,直接在这里勾选极性反转就能解决。这是一个非常实用的feature,调试时能省一版PCB。
3.3 用户逻辑与IP核对接
配置完IP核并生成示例设计(Example Design)后,Vivado会生成一个完整的测试工程,包含参考时钟模块、复位模块、GTX Example Design等。我们通常在这个基础上改动用户逻辑。IP核对外接口主要分两组:一组是事务层接口(ireq/treq/insp/trsp等信号组),一组是维护接口(maint_req/maint_rd等)。
以HELLO包格式为例,写一个发送模块。HELLO包的包头是64bit,需要填充数据头和物理层标识。下面是一段简化的Verilog发送逻辑片段,这份代码可以直接嵌入到用户工程中:
// 构造一个简单的HELLO NWRITE请求 wire s_axis_ioreq_tvalid; wire s_axis_ioreq_tready; wire [63:0] s_axis_ioreq_tdata; wire [31:0] s_axis_ioreq_tuser; wire s_axis_ioreq_tlast; assign s_axis_ioreq_tdata[63:32] = 32'h00000001; // 表示转发的目的地址 assign s_axis_ioreq_tdata[31:27] = 5'b00101; // HELLO包头固定编码 assign s_axis_ioreq_tdata[26:24] = 3'b000; // 保留 assign s_axis_ioreq_tdata[23:16] = 8'd8; // 包类型:NWRITE assign s_axis_ioreq_tdata[15:8] = 8'd0; // 目标TID assign s_axis_ioreq_tdata[7:0] = 8'd0; // 源TID这里重点提示:HELLO包格式是一种简化的事务层映射方式,它把地址、长度、读写类型等信息全部压缩在64bit的包头里。对于FPGA工程师来说,这种格式最容易理解和处理,所以大量例程都用HELLO模式。但是Xilinx IP核默认也支持DirectIO模式,DirectIO的包头格式和HELLO不同,如果你在例程中看到tdata是128bit且字段位置不同,不要慌张,看看配置里是否勾选了HELLO格式。
接收模块的调试技巧是,先不要急着解析Payload,先抓tdata和tvalid/tready握手信号。每收到一个完整的包,tlast会拉高,此时记录包的个数,如果数量匹配发送端,再进一步解析数据内容和地址映射。我在调试时会在用户逻辑里挂一个ILA(集成逻辑分析仪),在接收侧抓tvalid/tready/tdata/tlast,这样可以非常直观地看到包到达的时间、握手时序是否正常。
4. 时序约束与调试实录
4.1 时钟与复位设计
SRIO IP核的时钟系统比普通逻辑复杂一些。它大体上有三类时钟:收发器参考时钟(通常由外部晶振或时钟芯片输入,频率必须精确匹配所选线速率档位)、用户侧时钟(事务层接口时钟,通常等于逻辑数据宽度除以线速率换算得到的时钟,例如线速率5 Gbps,64bit接口时时钟约为156.25 MHz)、以及IP核内部的高速并行时钟。
参考时钟的质量非常重要,我当时用的板卡上有一颗100M差分晶振,却想配置成3.125 Gbps速率,结果GTX怎么都无法训练成功。后来检查原理图,才发现参考时钟应该用125M,更换时钟芯片后问题直接消失。所以在设计PCB或者选用开发板时,务必要确认参考时钟频率是否覆盖目标线速率倍频要求。另外,复位信号要满足IP核的时序要求,绝对不能简单用全局异步复位,否则会出现初始化状态机卡死的隐患。
4.2 常见失败模式与排查思路
我整理了一份SRIO链路调试时的排查顺序清单,按优先级排列:
- 检查物理层状态:通过维护读操作读取端口0的
ERROR_DETECTED寄存器,如果该寄存器非零,表示链路已经发生错误重传。 - 检查链路状态:维护读
PORT_STATUS寄存器,值为0x2表示训练完成,0x1表示正在训练,0x0表示没有检测到对端设备。 - 检查参考时钟和电源:用示波器测量参考时钟波形,用频谱仪看GTX电源的纹波。很多时候问题是电源噪声导致误码率偏高,链路反复训练不成功。
- 检查复位顺序:确保
mmcm_lock和pll_lock已经拉高,再释放IP核的arst_n信号。 - 检查对端配置:对端设备的端口配置必须与FPGA一致,包括链路宽度、速率、设备ID宽度。
4.3 实操中的一些坑与心得
在跑SRIO例程时,最容易踩的坑是“错误地认为握手信号就代表数据传输成功”。一次项目中,我观察到IP核的tx_axis_ireq_tready一直为高,就以为发送链路没有问题。但实际上,因为对端DSP的回保中添加了路由规则,我发出的包全部被置为“丢弃”,收端根本没有任何反应。后来我通过对端DSP回报的调试窗口才看出包已经到达交换机端口但丢弃了。由此建议,SRIO例程必须要有主动回报(比如维护读目的端寄存器成功才视为链路通)这一步,才算真正调试通过。
另一个值得注意的点是“数据位宽与包长度的对应关系”。例如在64bit接口下,一个包含128字节Payload的包可能需要发送多个周期。如果你的用户逻辑发送的Payload长度不满足4字节对齐,IP核会自动填充,但接收侧会因为填充字节而多出无常数据,导致有效数据提取变得困难。因此强烈建议所有发送数据都按8字节对齐,避免不必要的麻烦。
关于ILA的采样深度的选择,时域上SRIO包频率高,建议采样深度设为65536以上,并开启capture control的trigger position设定为“中间”,这样能看到包的前后上下文,方便分析。
5. 常见问题速查与建议
5.1 问题速查表
结合自己和行业社区中经常遇到的SRIO问题,整理成下表,方便直接对照处理:
| 现象 | 可能原因 | 排查/解决办法 |
|---|---|---|
链路始终处于Port OK = 0 | 对端未上电、参考时钟未到、线速率不匹配 | 测量参考时钟;确认对端配置;检查复位 |
| 链路偶尔训练成功,但高负载时丢包 | 电源纹波大、信号完整性差、接触不良 | 检查GTX电源纹波、检查高速连接器、降低速率 |
| 能从本地维护寄存器读回数据,但读不到对端 | 路由表缺失或DeviceID配置错误 | 检查对端路由配置,确认DeviceID宽度一致 |
发送端tready一直为低 | 用户逻辑包之间间隔过短,IP核内部FIFO满 | 拉大tvalid/tlast之间的空隙,或改用tkeep模式 |
| 收到的数据偶尔多出4字节 | Payload未8字节对齐,IP核自动填充 | 发送侧统一8字节对齐 |
| 调试时ILA抓不到包 | ILA时钟选择错误,采样深度不足 | 将采样时钟改为用户时钟axi_cq_clk,深度加大 |
5.2 工程师视角的几点补充建议
这条例程如果再往下深入,可以考虑扩展为“多通道SRIO + DMA”的架构。DMA可以把传输负载从CPU中解放出来,FPGA侧使用XDMA或者Axi-DMA,把SRIO收到的数据直接搬运到DDR中,再把DDR中的数据通过SRIO发送出去。这种方式的吞吐量远高于逐包处理的方式,在雷达回波采集、基带信号处理场景里很常见。
我在实际工程中体会最深的一点是:SRIO例程的“跑通”和“跑稳”是两件事。跑通只需要链路训练成功并发送一两个包,跑稳则需要长时间压测、错误统计、链路重训练策略都要考虑。Xilinx IP核提供了一些可选的错误恢复机制,比如Link-level retry和Error recovery,但需要用户配合维护模块定期读取状态寄存器,才能实现自动化恢复。一般在工程稳定后,我会做一个看门狗逻辑,周期检测PORT_STATUS,如果不是正常状态,就自动复位并重新初始化链路。
另外,关于仿真,Netlist仿真或IP核的Behavioral仿真都比较慢,且不支持GTX的模拟,所以SRIO例程的仿真验证重点放在事务层和逻辑层部分,物理层则依赖上板实测。如果能使用Zynq平台,搭配ARM跑Linux,通过PGP(PCIe Generic Packet)调SRIO也是一种路径,不过那属于更外延的话题了。当前这个FPGA SRIO例程,只要能按照上面步骤把链路拉起来,把维护读写跑通,你就已经迈过了SRIO开发的最大门槛,后面无论是做DSP互连还是背板交换,剩下的都是“数据怎么组织”的问题了。
本文还有配套的精品资源,点击获取