简介本资源是一份面向嵌入式开发工程师与DSP系统学习者的TI C6678平台SRIO通信实战代码包聚焦Serial RapidIO高速互连技术在多核DSP场景下的初始化、设备枚举与基础通信实现。压缩包仅含1个核心C源文件test_srio.c大小为8KB完整覆盖6678系统启动配置、SRIO控制器寄存器级初始化、端口枚举流程、中断服务例程及通信验证逻辑代码经实机测试可稳定运行适用于通信基站、雷达信号处理等对低延迟互连有严苛要求的工业与军工嵌入式项目。资源已获635人学习下载读者可直接复用该轻量级SRIO驱动框架快速掌握C6678平台下PHY/MAC层协同配置方法、设备ID解析与资源分配机制并基于其结构扩展数据传输、错误恢复等高级功能。 最近在整理老工程时翻出来一个 6678 SRIO 的源代码项目这个包当时是从一块评估板项目里抽出来的被同事压缩成 6678_srio_program.rar 后一直躺在网盘里。里面包含了完整的 CCS 工程、LSU/Doorbell 的初始化代码、收发测试函数以及一部分硬件配置说明。对正在搞 TI 6678 DSP 和 FPGA 高速互连的朋友来说这份代码有很高的参考价值因为它把 Serial RapidIO 最常用的 DirectIO 和 Doorbell 两条路径都写清楚了。这篇文章就借着这份源代码聊聊 6678 SRIO 的架构理解、代码关键点、板级调试流程以及我在实际调板过程中踩过的那些坑。这个主题偏实战所以我默认你已经有 CCS 和 6678 的基础但 SRIO 还不太熟。如果你刚接触 KeyStone 架构也不用担心我会把寄存器和传输流程尽量掰开讲让你拿到代码后能直接照着跑起来。1. 项目背景与 SRIO 基础认知1.1 6678 的 SRIO 外设能干什么TMS320C6678 是 TI KeyStone 系列里的八核 DSP内部集成了一个 Serial RapidIO 2.1 控制器最高支持 4 lane、每 lane 5 Gbaud。这个外设最大的价值是让 DSP 跟 FPGA、其他 DSP、或者交换芯片之间建立一条低延迟、高带宽的串行互连通道。相比并行 EMIF 接口SRIO 的引脚更少、速率更高相比 PCIeSRIO 在嵌入式领域里对包交换和点对点传输的支持更直接很多雷达、图像处理和通信设备都会用到它。源码包里的工程名称就叫 6678_srio_program目标非常明确演示一个最基本的 SRIO 通信链路。它做的事情概括起来就两件一是通过 LSULoad/Store Unit发起 DirectIO 读写把 DSP 侧的数据写到远端设备的地址空间二是通过 Doorbell 中断接收远端设备发来的通知。这两件事覆盖了 SRIO 日常使用的绝大部分场景所以把这份代码吃透后面再接触 RapidIO 交换机、多核并发传输就有了基础。1.2 为什么选择 SRIO 而不是 PCIe 或 EMIF有朋友问过我6678 上不是也有 PCIe 吗为什么非要用 SRIO我个人的理解是这主要取决于互连的对端是什么。如果对端是 x86 主机或者 GPUPCIe 自然是首选但如果对端是 FPGA 或另外一个 DSPSRIO 更合适。RapidIO 协议在设计时就很强调嵌入式板间通信支持直接内存访问语义、消息传递和全局寻址硬件上又能跑在背板、短距离 PCB 走线甚至光纤上这些特性让它在信号处理板卡中相当有存在感。另一个原因是软件生态。TI 对 KeyStone 系列的 SRIO 提供了很完整的驱动库和例程源码包里的初始化逻辑其实就是基于 TI 的 CSLChip Support Library和 Platform Library 改出来的。相比自己去抠 PHY 层时序直接基于官方寄存器映射写代码会更稳也更容易在 CCS 里单步调试。换句话说如果你只是想在 6678 和 FPGA 之间跑高速数据先跑通 SRIO 再考虑其他接口是一个很务实的路径。1.3 拿到源码包后先看懂整体结构解压 6678_srio_program.rar 之后第一眼看到的目录结构通常是这样的src/C 源码目录包含 main.c、srio_init.c、srio_transfer.c 等文件include/头文件目录包含 srio_macros.h、cslr_srio.h 等cfg/平台的 .cmd 链接文件和启动配置project/CCS 工程文件目录带 .project 和 .cprojectdoc/简单的说明文档和寄存器速查表建议阅读顺序是先把 main.c 里 SRIO 相关调用做一个索引再去看 srio_init.c 里的初始化函数然后是 LSU 和 Doorbell 的收发实现。代码里很多寄存器操作看起来很枯燥但最好对照 TRMTechnical Reference Manual里 SRIO 章节的寄存器来描述看一遍比如 LSU 有 0 到 15 一共 16 组寄存器每组都包含 LSU_Reg0 到 LSU_Reg6这样后续修改地址的时候才不容易晕。2. 核心模块代码拆解2.1 main 函数里的初始化顺序这份代码的 main 函数保持了一个很清晰的初始化链关中断、初始化 DDR3、初始化 SRIO、配置 Doorbell、发起一个自回环测试或外部回环测试。实际项目中SRIO 初始化必须在使能中断之前完成因为一旦远端的 FPGA 在链路训练完成后马上发 Doorbell中断服务函数可能还没挂好就丢中断了。我重点看的是SrioInit()这个函数它内部会做这样几件事配置 PLL产生 SRIO 模块所需的参考时钟设置端口协议和 lane 数比如 4x 或 1x配置 LSU 寄存器组默认指向远端的某个设备地址设置 Doorbell 路由表和中断映射把 SRIO 端口使能并等待 link 状态变为 OK之前调试时为了图快我一度跳过 PLL 配置直接把 SRIO 模块打开结果链路怎么都训练不上。后来补上 PLL 配后发现SRIO 的 core clock 和 serdes clock 必须按要求来频率算错的话 PHY 层会直接掉链子。所以拿到别人代码时第一步不要乱砍先把初始化链路顺序保留住再根据板卡实际时钟去改倍频系数。2.2 LSU 寄存器控制的 DirectIO 传输SRIO 的 DirectIO 操作其实很像 DMADSP 告诉 SRIO 控制器把本端某块内存写到远端某个地址或者从远端某地址读到本端内存。这份代码里用到了 LSU 寄存器组核心是填充下面几个字段LSU_Reg0远端设备 ID 和传输类型LSU_Reg2远端目标地址的低 32 位LSU_Reg3远端目标地址的高 32 位如果需要 64 位地址LSU_Reg4本端源地址的低 32 位LSU_Reg5本端源地址的高 32 位、数据字节数、优先级LSU_Reg6控制字段比如发起传输、请求完成、中断使能等代码里一般会写一个类似SrioDirectIORead()或SrioDirectIOWrite()的函数把参数组包后写入 LSU 寄存器然后读 LSU_Reg6 的 busy 位来判断传输是否完成。注意寄存器写入顺序是很讲究的通常是先把地址、长度、控制字段都配好最后再触发。如果你一边写一边触发很容易造成 LSU 控制逻辑状态异常。一个很容易忽略的点是LSU 的地址都是物理地址不是虚拟地址。6678 在 KeyStone 中通常把共享内存映射到 0x0C000000所以如果你想从 DDR3 拿数据发出去务必确认 DDR3 映射在哪个地址段然后在 LSU 源地址字段写对应的物理地址。否则不发神发一堆 0xDEADBEEF 出来也是常有的事。2.3 Doorbell 中断机制与代码实现Doorbell 是 RapidIO 里非常轻量的消息通知机制本质上就是一个 32 位的信息字段。远端设备可以发一个 Doorbell 包给 6678而这个包不携带大量数据主要用于“告诉 DSP 我这边数据准备好了”或者“链路状态变了”。源码包里对应的中断流程大致是在 SRIO 模块初始化时把 Doorbell 到达事件映射到某个中断组合再通过 KeyStone 的 Event Combiner 映射到 CPU 中断中断服务函数里读取 Doorbell 寄存器解析信息字段清中断标志主循环里通过信号量或全局标志来响应这次 Doorbell 事件这一套流程里最容易出问题的不是配置而是中断清标志。SRIO 的 Doorbell 状态寄存器里会有多个 event 位有些位是写 1 清零有些位是读后清零。如果你没有仔细看寄存器说明直接写一个 0 或者写错位中断标志清不掉ISR 就会反复触发几乎卡死整个应用。代码里一般会用SRIO_DOORBELL_ICRR这类宏来辅助清标志但宏定义在不同 CSL 版本里叫法可能不同移植时要特别留意。2.4 工程文件与编译环境注意事项这个工程是用 TI CCS 建的版本大概在 5.x 到 6.x 之间。CCS 里导入工程时要注意 Compiler、SYS/BIOS 和 CSL 的版本。尤其是 CSL 头文件不同版本的寄存器位域定义会有些差别比如有的版本把LSU_Reg2里的地址字段拆成了多个位域有的版本直接给了 32 位变量。如果你的项目自带了一套 CSL而源码包里的 include 路径跟你本机安装的版本不一致编译时会有大量红色报错。遇到这种问题最好的办法不是全覆盖而是对照 TI 官方 CSL 文档把差异部分一处处改掉。还要注意 .cmd 文件里的内存映射。SRIO 收发测试一般会用到 MSMC0x0C000000和 DDR30x80000000 起。如果 .cmd 里没有给 SRIO 收发缓冲区安排足够的内存段运行起来很容易踩到未初始化区域导致 LSU 读到非法 memory表现就是传输完成中断永远不来。我在调试时最常做的就是先在 main 函数里给缓冲区做一次固定赋值确保内存可读写再开启 SRIO 传输。3. 从 CCS 到板卡的完整实操流程3.1 硬件连接与准备在跑代码之前先确认硬件拓扑。最常见的场景是 6678 DSP 板和一块 FPGA 板通过 SMA 或板载走线连接 SRIO。我手里的设备是 6678 评估板 Xilinx FPGA 开发板中间通过一个 4x 的 SRIO 接口连接。两端都要配置成相同的速率和 lane 数比如都是 3.125 Gbaud 的 4x或者 5 Gbaud 的 1x。硬件准备阶段需要测量参考时钟。SRIO 的 SerDes 参考时钟通常由板上的时钟芯片提供6678 要求这个时钟的精度尽量高一般是 125 MHz 或 156.25 MHz。如果时钟漂移大链路训练很难稳定。另外SRIO 的 lane 布线一般有等长要求但做系统集成时不一定能完全保证。如果链路偶发训练失败优先查时钟源和电源纹波不要一上来就改代码。接下来就是给板卡上电连接 CCS 仿真器XDS200V2 或 XDS560。我会先用 CCS 的 Target Configuration 连接 6678 内核然后检查 GEL 文件里的 PLL 初始化是否正常确保 CPU 主频稳定。这一步没做好后面所有外设都会连带出问题。3.2 导入工程并编译烧写打开 CCS 后直接选择 Import CCS Eclipse Project然后选到解压后的 project 目录。如果工程类型是老的 CCS 3.3 格式CCS 6 以上版本可能不能直接识别需要手动创建空工程然后把 src 下的 .c 文件全部拖进去再把 include 路径和 .cmd 文件路径配好。这个过程比较机械但能锻炼对工程结构的理解。编译前要检查的配置项有编译选项里的目标 CPU 型号确保是 6678优化级别建议先用 -O0 调试跑通以后再开 -O2 或 -O3Include path 中是否包含 CSL、XDC 目录特别是 SRIO 相关头文件是否使能了 SYS/BIOS如果项目里用了 SYS/BIOS要注意 Heap 大小和中断配置编译成功后把生成的 .out 下载到 6678 的 L2 SRAM 上跑还是一般直接加载到 DDR3 跑我个人的习惯是先加载到 L2 SRAM 里跑一个最小测试因为不依赖 DDR3 初始化能快速验证 SRIO 时钟和初始化函数。等最小测试把链路跑起来再把代码放到 DDR3 里做性能测试这样效率更高。3.3 跑通一个简单的 SRIO 读写测试源码包里一般会提供两个函数一个是写测试一个是读测试。写测试的逻辑是DSP 侧初始化一段源缓冲区往里面填充逐字节递增的数据然后通过 LSU 指令把这段数据写到远端 FPGA 的 BAR 地址。读测试逻辑反过来从远端地址读一段数据到 DSP 的接收缓冲区然后打印出来看是否与期望值一致。在实际跑测试时FPGA 侧也要有对应的逻辑响应。如果 FPGA 里只做了 RapidIO 端点那么 DirectIO 写请求到达后数据会落入 FPGA 内部寄存器或者 Block RAM读请求则需要 FPGA 返回 Completer 或者 NWrite 响应。如果 FPGA 逻辑没有正确处理DSP 侧 LSU 会一直处于 busy 或返回 error。所以两边最好先协商好地址映射表。例如FPGA 端接收窗口0x00000000 ~ 0x0000FFFFFPGA 端发送缓冲区DSP 读取0x00010000 ~ 0x0001FFFFDSP 侧的 LSU 配置时远端地址就写 FPGA 映射后的地址。我在调试时经常对着 TRM 看 FPGA 侧逻辑两边交叉验证比单看 DSP 代码容易定位问题。3.4 实测速率与性能优化空间当读写测试稳定后就可以测性能了。最简单的测法是用 6678 的计时器统计一次大块 DirectIO 写入的时间然后除以字节数得到带宽。但直接这样测并不准因为 LSU 触发后 CPU 会等待门铃中断或 busy 位清除。更好的方式是配合 SRIO 的 EDMA 或在多个 LSU 寄存器组之间轮流发请求把 LSU 的流水线利用起来。源码包里的示例可能只用了 LSU 0最大带宽肯定上不去。要知道 SRIO 在 4x 5 Gbaud 时理论带宽约 5 GBaud * 4 * 8/10 16 Gbps也就是 2 GB/s但因为协议开销实际有效带宽通常在 7 到 12 Gbps 之间具体取决于包大小、读/写方向和 FPGA 端处理能力。如果想提升吞吐建议用更大的 burst比如每次写 4 KB 以上并开启 multiple LSU request尽量避免小包传输。还有一个优化点是对齐。SRIO 对 address 和 length 的对齐要求比较严格比如 32-bit 传输时地址最好 4 字节对齐64-bit 时最好 8 字节对齐。不对齐会导致 LSU 报错或者自动拆包影响效率。代码里可以使用#pragma DATA_ALIGN来确保缓冲区对齐。4. 常见问题与排查记录4.1 链路无法训练成功的排查套路链路训练失败是 SRIO 调试里最常见也最头痛的问题。我在这个项目中遇到的典型现象是SrioInit 初始化完成后读端口状态寄存器PORT_STAT一直显示UNINITIALIZED或者ERROR。排查时我会按下面这个顺序走先看 PHY 参考时钟是否都有并且频率正确。用示波器测量 SRIO 参考时钟引脚频率切到 125 MHz看波形幅度是否足够。再看两端 lane 数、速率是否一致。如果 FPGA 配的是 1x 5 GbaudDSP 这边却配的是 4x 3.125 Gbaud肯定训练失败。检查 SerDes 配置特别是 TX 的 swing、pre-emphasis以及 RX 的 equalization。6678 内部的 SerDes 参数在SrioInit里有一组默认值不一定适配所有板卡。如果链路抖动大适当调 TX 驱动强度或者打开 RX adaptive equalizer。用 SRIO 自回环模式测试。代码包里如果有 loopback 测试宏先把 SRIO 配置成 internal loopback确认 DSP 内部 SerDes 和 MAC 层的通路没有问题再改成外部传输。我在调板时发现了一个很有意思的问题初始代码里的 SerDes 配置是基于 156.25 MHz 参考时钟的但我的板卡实际用的是 125 MHz 时钟。链路训练一直失败后来我把 CDR 分频系数和 PLL 倍频系数改成对应 125 MHz 的参数问题立刻消失。这个案例说明初始化代码里的 SerDes 表格必须跟板级实际时钟匹配不能照搬。4.2 Doorbell 中断收不到的常见原因Doorbell 中断收不到多半是中断映射或清标志环节出了问题。先用 SRIO 寄存器里的 Debug 寄存器看 Doorbell 事件是否已经到达。如果到达了但 CPU 没有进中断那就查中断路由确认 SRIO Doorbell event 是否组合到了正确的 CorePac 中断输入确认中断控制器里对应 event 是否 enable确认 ISR 里是否做了ache无效化操作或者是不是中断栈空间不足还有一个小坑是 Doorbell 的 ID 过滤。SRIO 可以按源设备 ID 白名单过滤 Doorbell如果初始化时没有把你远端设备的 ID 加入白名单Doorbell 包就会被硬件丢掉。我在调试中就用到了这个过滤功能但一开始没配好结果 FPGA 发的 Doorbell 永远到不了 DSP排查了好久才发现是 ID 匹配规则里写反了。4.3 地址映射和数据对齐问题DirectIO 场景下最容易出现数据错乱。我遇到过一个很经典的现象DSP 向 FPGA 写 1024 字节FPGA 收到的数据前一半是正确的后一半变成了上一次发送的旧数据。原因就是 DSP 侧源地址没有 8 字节对齐导致 LSU 把一块非对齐的内存按照多个 segment 传输而 FPGA 侧的缓冲处理没有按 segment 边界切分。后来我把收发缓冲区都用#pragma DATA_ALIGN强制 8 字节对齐并保证传输长度也是 8 的整数倍问题就消失了。另外如果你的系统是多核共享内存核之间很可能因为 cache 一致性问题导致 SRIO 发出去的数据是旧值。6678 的 L2 cache 是写回策略如果 CPU 先写了源缓冲然后立刻触发 LSU 传输LSU 可能读的是 L2 cache 里的脏数据但外部看到的已经是过期的数据。解决办法是在发起 SRIO 传输之前对源缓冲区做CACHE_wbL2操作在接收数据之后对目的缓冲区做CACHE_invL2操作。4.4 版本兼容性与后续扩展建议这份源代码主要是参考性质直接拿到新板子上编译时要留意 CSL 版本和 BSP 版本差异。由于 TI 更新了 CSL 库某些寄存器结构体名称和字段会改变。比如旧版本使用CSL_SRIO_LSU_READ_WRITE宏新版本可能推荐直接操作CSL_SrioRegs指针。建议不要把代码粘进新工程就算完而是根据实际使用到的字段去对照寄存器 map逐项确认。如果后续希望做更复杂的应用可以在现有基础上扩展利用多个 LSU 寄存器组并行提高带宽增加 Type 9/Type 5 消息传输配合 Doorbell 做核间消息接入 SYS/BIOS使用中断信号量来同步 SRIO 完成事件加上错误恢复逻辑比如超时重发或链路重建我在实际项目里最终把 SRIO 收到数据后的处理放在 DSP 核 0主循环里轮询 Doorbell 标志而核 1 到核 7 各自处理信号处理任务通过共享队列和 SRIO 互联起来整体能跑得比较稳。这个架构就是从这份基础代码一步步改出来的。最后分享一个小技巧在调试 SRIO 时尽量在 LSU 寄存器操作前后加一些 printf 或日志打印把提交的源地址、目标地址、长度都打出来。因为 SRIO 寄存器操作的错误很多时候是配置参数的问题直接把参数打到 Debug 窗口能省下不少端到端排查的时间。这个习惯我一直保留到现在尤其是在调 FPGA 和 DSP 联合调试时非常实用。本文还有配套的精品资源点击获取