ARTICLE DETAIL

资讯详情

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

C6678 SRIO通信源码全解析:从寄存器到高速数据搬运指南

C6678 SRIO通信源码全解析:从寄存器到高速数据搬运指南 简介本资源是一份面向嵌入式开发工程师与DSP系统学习者的TI C6678平台SRIO通信实战代码包聚焦Serial RapidIO高速互连技术在多核DSP场景下的初始化、设备枚举与基础通信实现。压缩包仅含1个核心C源文件test_srio.c大小为8KB代码已通过硬件实测验证可稳定完成6678系统启动配置、SRIO物理层与传输层初始化、网络设备自动发现与拓扑识别等关键流程。资源涵盖完整的寄存器级操作、中断服务例程、数据包收发框架及调试断言逻辑特别适合深入理解SRIO协议栈分层机制PHY/MAC/Transport、掌握C6678多核内存映射与中断向量配置并为构建分布式信号处理系统提供可复用的底层通信模块。目前已有635人学习下载是开展SRIO驱动开发、嵌入式高速互连实验及竞赛项目底层通信模块搭建的高价值参考实现。 先从结论说起这次放出来的6678_srio_program.rar是目前网上能见到的、比较完整的一套 TI TMS320C6678 多核 DSP 的 SRIOSerial RapidIO通信源代码。压缩包里包含从 SRIO 端口初始化、链路协商、Doorbell 中断处理到基于 NWRITE/NREAD 的数据收发示例基本覆盖了在 6678 上跑通 SRIO 所需的核心代码路径。如果你正在做基于 C6678 的板卡开发或者需要给 FPGA 和 DSP 之间建立高速数据通路这套代码可以作为第一版的参考基础直接拿来改。我在实际项目中用 6678 的 SRIO 模块和 Xilinx FPGA 做过联调磕磕绊绊踩了不少坑。这套代码我之前也研究过里面一些写法虽然不算最优但胜在结构清晰、注释到位对于第一次接触 SRIO 协议栈和 Keystone 架构的开发者来说比啃 2000 多页的 SPRUGW1B 手册要友好得多。这篇文章我按自己的理解把这个项目拆开讲一遍包括代码架构、关键寄存器配置、和我们实际部署时验证过的细节同时把网上资料里容易忽略的一些坑也一并列出来。1. 为什么 6678 的数据交换场景绕不开 SRIOC6678 是 TI Keystone 架构的旗舰多核 DSP8 个 C66x 内核最高 1.25GHz单芯片浮点性能标称 160 GMACs / 320 GFLOPS。但算力再强数据进不来、出不去也是白搭。6678 对外的高速接口主要有 SRIO、PCIe、HyperLink 和 GbE 几种不同的接口承担不同的职责。SRIO 在这其中扮演的角色是低延迟、高带宽、CPU 开销小的内存映射式数据传输通道。我在选型的时候对比过几个方案。PCIe 在 6678 上做 Root Complex 的支持比较有限而且 PCIe 的软件模型偏复杂DMA 描述符管理也繁琐不太适合做实时数据流的搬运GbE 的软件栈开销太大TCP/IP 协议栈一跑延迟直接上百微秒数据面根本扛不住高吞吐HyperLink 是 TI 私有的接口只能连自家的器件无法接 FPGA 或者第三方处理器。SRIO 是公共标准协议最高支持 5 Gbaud/lane 的速率x4 配置下理论带宽能到 20 Gbps而且协议天然支持内存映射读写DSP 可以直接发起对远端设备内存的读写这个特性对雷达、图像处理这类数据密集型应用来说太关键了。再说延迟。RapidIO 的协议设计非常精简事务层、传输层、物理层三层架构没有像 TCP 那样的复杂握手也没有 PCIe 那样重的链路管理和配置机制。实测下来单次 NWRITE 从 DSP 端发起、到 FPGA 端收到数据硬件层面的延迟大概在几百纳秒量级。配合 TI 的 SRIO DriverCSL 或者 PDK 里的 Srio LLDCPU 只需要准备好描述符、发起一次寄存器写操作剩下的数据搬运全部由 SRIO 外设的 DMA 引擎完成传输完成后通过 Doorbell 中断通知 CPU。这种模式在整个信号处理链路里CPU 几乎不会被数据传输过程拖累。对于 6678 来说SRIO 还有一层更实际的意义它解决了多板卡间的数据交换问题。一个处理系统里往往有多块 DSP 板卡或者一块 DSP 搭配一块 FPGA 做前端采集。如果用 SPI、UART 这类低速接口搬数据带宽完全不够看用并行总线又受限于 board 走线和引脚数。SRIO 通过串行差分对传输x4 模式只需要 8 根信号线板级设计难度大大降低而且可以直接通过交换机芯片比如 Tsi578、CPS1848构建多节点交换网络扩展性比点对点的 HyperLink 好太多了。所以回到这套源码它解决的不是让 SRIO 跑起来这种入门问题而是让 SRIO 在 6678 上稳定、高效地把数据从 A 点搬到 B 点这个实际工程问题。理解了这一点再去看代码里的每个模块思路会清晰很多。2. 代码结构与核心文件这套源码里到底装了什么东西拿到 RAR 压缩包解压之后不要急着看代码先把文件结构捋一遍。这套代码基于 TI 的 MCSDKMulticore Software Development Kit框架目录组织方式跟 PDKPlatform Development Kit里的 Srio 例程有些类似但做了不少针对性的裁剪和修改。我按实际执行顺序把文件过一遍。2.1 工程入口与初始化主流程项目根目录下一般会有 CCS 工程文件.project / .cproject或者 makefile。入口文件通常是main.c或者srio_test.c主要完成以下几个步骤配置 PLL把 DSP 主频和 SRIO 参考时钟调整到目标频率调用SRIO_Init()完成 SRIO 外设的全局初始化配置 LSULoad Store Unit寄存器建立本地地址到 SRIO 地址空间的映射注册 Doorbell 中断处理函数初始化一个测试缓冲区发起一轮数据读写测试void main(void) { // 配置 DSP 主频和 SRIO 参考时钟 // 具体值取决于板卡设计参考时钟一般接入 312.5MHz 或 250MHz PLL_Init(); // 初始化 SRIO 外设 SRIO_Init(); // 设置 LSU 地址映射本地物理地址 - SRIO 全局地址 LSU_Setup(); // 注册 Doorbell 中断 Doorbell_IntRegister(); // 发起 SRIO 写操作测试 SRIO_NWRITE_Test(); while(1); }这段流程看着简单但每一步展开都有不少细节。PLL 配置错误会导致 SRIO 参考时钟频率不对链路协商直接失败LSU 映射配错了地址翻译不对数据就会写到莫名其妙的地方。后面小节我会逐一拆解。2.2 关键头文件与寄存器定义代码里有几个比较核心的头文件srio_reg.h定义了 6678 SRIO 外设的寄存器结构体。这个文件在 TI 的 CSL 库里也有但源码里有些版本为了兼容老平台会自己维护一份寄存器宏定义。重点关注的寄存器包括LSU_Regs、DOORBELL_Regs、ERROR_Regs、PER_SET_CNTL等。srio_dev.h定义了 SRIO 设备 ID、端口号、链路速率等配置参数的结构体。实际项目里可能会基于这个文件扩展出自己的配置结构。srio_lnk.h链路相关配置包括端口模式1x/2x/4x、链路速率1.25/2.5/3.125/5 Gbaud、训练模式使能等。看代码的时候建议把 SPRUGW1BKeyStone Architecture Serial RapidIO User Guide放在手边对着寄存器手册看。SRIO 的寄存器字段非常多光靠记根本记不住。这套源码的好处是作者已经在关键寄存器配置的地方加了注释比如// 配置 LSU 寄存器 0 和 1 // LSU_Regs-LSU_0_REG_0 存放 SRIO 地址的低 32 位 // LSU_Regs-LSU_0_REG_1 高 4 位 控制字段 // 这里配置为 NWRITE (0x1)这类注释对快速理解代码逻辑非常有帮助。2.3 CSL、LLD 与裸寄存器三层编程模型的取舍TI 的 SRIO 支持三种编程方式直接操作寄存器裸寄存器、CSLChip Support Library、LLDLow-Level DriverPDK 里叫 Srio LLD。这套源码混合使用了 CSL 和裸寄存器两种方式。初始化部分调用了 CSL API比如CSL_SRIO_Init()而数据传输的关键路径则直接操作 LSU 寄存器。为什么这么做我个人的理解是CSL API 封装层次高适合做初始化这类低频操作数据传输路径上的代码用裸寄存器可以避免函数调用开销也更容易控制时序。SRIO 的数据传输速度是微秒级甚至纳秒级操作的场景多一层封装就可能多几条指令的开销。当然现代编译器在优化开满的情况下函数调用开销其实可以忽略但直接操作寄存器确实让代码的行为更明确、更容易调试。实际项目里如果你的软件框架已经用了 PDK 的 Srio LLD就没必要学这套代码用裸寄存器直接用 LLD 的Srio_send()、Srio_receive()接口会更省事。不过理解裸寄存器的操作方式仍然非常重要因为 LLD 底层最终还是操作这些寄存器。调试的时候LLD 报了错误码你还是得通过寄存器来查看具体是哪个环节出了问题。这套源码正好提供了一个从底层理解 SRIO 工作原理的入口。3. 从 CSL 到底层寄存器SRIO 初始化的关键路径拆解初始化是 SRIO 最容易出问题的环节。链路协商不上、端口一直处于 disabled 状态、训练失败大部分都是初始化阶段配置不对导致的。这章我按代码执行顺序把初始化过程中的几个关键节点逐个拆开讲。3.1 PLL 与参考时钟SRIO 的心脏配置SRIO 模块的参考时钟SRIO_CLK决定了物理层收发的基准速率。在 6678 的 EVM 板比如 TMDSEVM6678上SRIO_CLK 频率通常配置为 250MHz 或 312.5MHz。根据你要跑的链路速率PLL 倍频系数需要对应调整。SRIO 内部 PLL 的倍频关系大致是链路速率 SRIO_CLK × 倍频系数 / 分频系数例如 SRIO_CLK 250MHz倍频系数 20分频系数 1输出就是 5 GHz对应 5 Gbaud/lane 的链路速率。代码里PLL_Init()做的事就是对 SRIO 的 PLL 控制寄存器进行配置。需要特别注意SRIO 的 PLL 配置必须在 SRIO 模块上电后、端口使能之前完成而且倍频系数不是任意值得查手册里的合法配置表。如果配了一个表里没有的值PLL 可能失锁链路就永远协商不上。我的调试验证方法很简单板卡上电后先读PER_SET_CNTL寄存器的 PLL 状态位确认 PLL 锁定后再进行后续操作。这一步能筛掉一半以上的链路不通问题。// 等待 SRIO PLL 锁定 // PER_SET_CNTL 寄存器的 PLL_LOCK 位offset 0x178 while ((*(volatile Uint32 *)(SRIO_PER_SET_CNTL) 0x1) 0);3.2 端口模式与链路速率参数链路参数在srio_dev.h/srio_lnk.h里定义。核心配置包括参数可用值说明端口模式1x / 2x / 4x决定使用几条 lane 传输对应不同的带宽链路速率1.25 / 2.5 / 3.125 / 5 Gbaud每条 lane 的线速率训练模式Standard / RapidIO 2.x training与对端设备协商使用ACK 模式0 / 1决定流控和重传机制设备类Endpoint / Switch6678 一般配置为 Endpoint这些参数配置到PORT_MODE、PORT_N_CTL、PORT_N_REGS等寄存器中。设置链路速率时要特别注意PORT_N_CTL 寄存器里的速率配置不是直接写线速率值而是按照协议规定的编码方式。代码里通常会有宏定义来转换比如#define LINK_RATE_1X_1P25 (0x0) // 1.25 Gbaud #define LINK_RATE_1X_2P5 (0x1) // 2.5 Gbaud #define LINK_RATE_1X_3P125 (0x2) // 3.125 Gbaud #define LINK_RATE_1X_5P0 (0x3) // 5.0 Gbaud如果对端设备只支持 3.125 Gbaud而这边配置了 5 Gbaud训练过程就会失败链路状态一直卡在PORT_ERR或者UNINITIALIZED。所以联调之前一定要先确认对端设备的 SRIO 能力集对照PORT_N_ACKID_STAT等寄存器查看协商结果。3.3 LSU 地址映射SRIO 地址空间与本地内存空间的翻译SRIO 模块本身有一套独立的地址空间叫做 SRIO 地址空间。DSP 访问远端设备内存时需要通过 LSULoad/Store Unit发送 NREAD/NWRITE 请求。每个 LSU 单元都可以配置一条独立的映射规则将 SRIO 地址空间中的一段地址映射到本地物理地址。6678 SRIO 有 8 个 LSU 单元LSU0 ~ LSU7每个 LSU 可以独立配置。配置 LSU 需要读写 4 个寄存器LSU_Regs[i].LSU_Reg0SRIO 地址低 32 位LSU_Regs[i].LSU_Reg1SRIO 地址高 4 位 RTSV CTO 优先级等控制字段LSU_Regs[i].LSU_Reg2本地地址低 32 位映射的本地起始地址LSU_Regs[i].LSU_Reg3本地地址高 8 位 映射大小字段举个例子如果要把本地 DDR3 的某段物理地址映射到 SRIO 地址空间中的 0x0 起始地址配置流程是// 映射本地 DDR 地址到 SRIO 地址空间 // 这里假设远端设备如 FPGA地址空间 0x0 对应本地的 0x80000000 LSU_Regs[0].LSU_Reg0 0; LSU_Regs[0].LSU_Reg1 (0 24) | (LSU_AMODE_UBLK 2) | 0x1; // 使能 LSU_Regs[0].LSU_Reg2 0x80000000; LSU_Regs[0].LSU_Reg3 (0x0 24); // 通用块映射大小由 AMODE 决定这部分的坑主要在于AMODEAddress Mode的选择。SRIO 支持多种地址映射模式比如 UBLCUser Base Local 0/1、CBLCCompact Base Local 0/1、DBLCData Base Local 0/1等。不同模式对地址对齐、映射大小都有不同要求。比如 DBLC 模式要求本地地址映射段大小必须对齐到 16 字节而 LBLC 模式是 8 字节。如果配置不对访问时会产生地址错误中断数据读出来也是错的。3.4 设备 ID 与路由表SRIO 协议中每个端点设备都有一个唯一的 Device ID。发送数据时LSU 会把目标设备的 ID 写入LSU_Reg4DESTID 字段。如果是点对点直连两端设备的 Device ID 可以任意设置只要不冲突如果通过交换机互联则需要在交换机里配置路由表让交换机根据 Device ID 决定转发到哪个端口。6678 的 Device ID 配置在DEV_ID_REG寄存器里而 LSU 发送请求时目标 ID 是每个 LSU 单独配置的。代码里通常会有类似这样的初始化// 配置本端设备 ID 为 0x10 CSL_SRIO_SetDeviceID(SRIO, 0x10); // 配置 LSU0 的目标设备 ID 为 0x20对端 DSP 或 FPGA LSU_Regs[0].LSU_Reg4 (0x20 16);这个 Device ID 在调试的时候有很大用处。SRIO 维护了一个DEV_ID_REG和DEV_ID_ROUTE_REG通过交换机联调时只要两端 Device ID 不一样就可以通过读路由信息判断链路状态。之前联调过一个多板卡系统因为两块板卡不小心配了相同的 Device ID导致交换机路由混乱数据包在交换机里被丢弃。排查了半天最后就是用DEV_ID_REG对比各板卡 ID 才定位到问题。4. Doorbell 中断机制从写一次门铃到 CPU 响应中断的完整链路Doorbell 是 SRIO 协议里一个很巧妙的设计。它本质上是一个轻量级的、带消息语义的中断触发机制。一个 SRIO 门铃事务只有 8 字节16 bit 的 info 字段 16 bit 的 srcID。收到 Doorbell 事务后接收方可以产生一个中断CPU 在中断处理函数中读取 Doorbell 寄存器判断是哪个源设备发来的、携带了什么信息。在数据流场景中Doorbell 最常见的用途是发送方完成一批数据写入后通过 Doorbell 通知接收方数据已经写好了你可以开始处理了。这样做的好处是接收方不需要不断轮询数据区域大大降低了 CPU 占用。4.1 Doorbell 寄存器操作流程发送 Doorbell 的流程和 NWRITE 不太一样。NWRITE 走的是 LSU 单元的存储操作而 Doorbell 需要通过DOORBELL_Regs中的寄存器来发送。核心步骤// 发送 Doorbell // DSTID 字段写入目标设备 ID // Info 字段是要携带的消息内容比如数据块编号 DOORBELL_Regs-DSTID 0x20; DOORBELL_Regs-DRBELL_INFO 0x0001; // 携带的信息 DOORBELL_Regs-DRBELL_CTL 0x1; // 触发发送接收端收 Doorbell 后会产生一个中断前提是已经使能了 Doorbell 中断。中断处理函数里通过读DOORBELL_Regs的IRQ_SRC字段来判断中断产生原因然后做相应处理。4.2 Doorbell 中断与 CIC/CpIntc 的映射这块是 6678 架构里比较容易绕晕的地方。SRIO 外设产生的中断信号先送到 CICCore Interrupt ControllerCIC 将中断映射到 CPR 的某个 event再由 CPINTC 映射到具体某个核的 INTC。整个链路是SRIO 外设中断事件 - 事件汇聚 (CIC) - 事件分发 (CpIntc) - CPU INTC - ISR具体到代码里SRIO 的各种中断事件DOORBELL、ERROR、LSU 完成等会先映射到 CIC 的输入通道。不同板卡的映射可能不一样这部分代码的配置通常在platform_Init或者evm_Init里。调试时如果发现 Doorbell 中断触发不了可以先用 JTAG 挂上看看 CIC 寄存器的输出状态位有没有置位如果 CIC 收到了事件但 CPU 没进中断就要查 CIC 到 CPINTC 的映射是否配置正确。这里有我自己的一个教训曾经在移植这套代码到我们自己的板卡时因为 CIC 映射配置和 EVM 板不一样Doorbell 中断时好时坏。后来直接把 CIC 和 CPINTC 相关寄存器的配置原样读出来对照我们板卡的硬件设计手册重新查了一遍中断路由才定位到问题。所以在看这套代码的中断部分时一定不要照抄要结合自己板卡的中断映射表来改。4.3 Doorbell 中断处理中的实际坑Doorbell 中断处理看似简单但有几个注意点清中断标志的顺序。SRIO 的 Doorbell 中断标志必须在中断处理函数里显式清除而且要先读状态、后清标志顺序反了可能导致中断丢失或者误触发。多个 Doorbell 事务合并。如果对端快速连续发送多个 Doorbell寄存器里只会保留最新的信息或者产生多个中断挂起位。处理时要做好计数逻辑不能假设一次中断只对应一个 Doorbell。DPTDoorbell Port路由。在交换机环境中Doorbell 的路由方式和 NWRITE 一致也是根据 Device ID 进行路由。如果远端设备 ID 配置错误Doorbell 根本到不了目的地。5. 数据搬运的坑与性能调优NWRITE、NREAD 和 Cache 一致性初始化搞定、中断链路打通接下来就是真正的数据搬运。SRIO 的数据传输效率很大程度上取决于你怎么用它。同样的硬件平台会用的人能跑出接近线速的带宽不会用的人可能只有理论值的一半。这章节展开讲我从这套源码和实际项目中总结的优化经验。5.1 NWRITE 和 NWRITE_R 的选择SRIO 传输类型中NWRITE 是写不等待响应的操作NWRITE_R 是写且要求响应的操作SWRITE 是更高效的流式写。在代码里你会发现作者主要用的是 NWRITE少量用到了 SWRITE。为什么因为NWRITE 不需要等待远端响应发送方发完就完事了带宽利用率最高。NWRITE_R 虽然多了一个响应包但保证了传输的可靠性——接收方收到数据后会返回一个响应发送方通过 LSU 的完成中断确认传输成功。在我们的项目中大量数据传输用 NWRITE关键的数据块结束标志用 Doorbell 通知而不是用 NWRITE_R 来确认。这样既保证了数据吞吐又能通过 Doorbell 实现同步。如果你一开始就用 NWRITE_R 做每一笔传输等响应的时间都够你再发好几笔数据了。5.2 缓冲区对齐与 Cache 一致性6678 的 L2 Cache 和 DDR 的 Cache 一致性问题是新手最容易踩的坑。SRIO 外设是总线主设备它的 DMA 读写不经过 DSP 的 Cache直接访问物理内存。因此如果 DSP 先写了数据到缓冲区然后 SRIO 读取DSP 必须先把 Cache 里的数据写回writeback到内存SRIO 才能读到最新数据。如果 SRIO 先把数据写到内存然后 DSP 要读DSP 必须先使 Cache 失效invalidateCPU 才能真正读到 SRIO 写入的数据。具体到代码里发送前要做这样的操作// 发送前将发送缓冲区的 Cache 内容写回内存 CACHE_wbL2(sendBuf, size, CACHE_WAIT); // 或者使用 DSPLib 提供的宏 // cache_writeback_all();接收后、CPU 处理数据前// 接收后使 Cache 失效重新从内存加载 CACHE_invL2(recvBuf, size, CACHE_WAIT);如果这步不做会出现非常诡异的现象DSP 往缓冲区写了数据SRIO 收过去的却是旧数据或者 FPGA 写过来的数据DSP 读到的还是清零之前的内容。我在第一个 SRIO 项目里就被这个坑折磨了整整两天最后翻手册看到 Cache 一致性章节才恍然大悟。这套源码里在发送和接收部分都做了 Cache 操作但注释可能不够醒目看代码的时候要留意到这些细节。5.3 Payload 大小与性能的关系SRIO 单个数据包的最大 payload 取决于维护包协商结果在 6678 上一般支持最大 256 字节。如果每次传输的数据块大于 256 字节硬件会把数据拆分成多个包发送。发送性能的关健之一是尽量把 LSU 的传输长度配置得大减少 LSU 发起次数。我在实测中对比过同样是发 4KB 数据用 4 次 1KB 的 LSU 传输比用 16 次 256 字节的传输快得多因为每次 LSU 发起都有固定开销寄存器写、请求仲裁、链路排队。实际操作中建议把单次 LSU 传输长度设在 1KB 到 4KB 之间既兼顾了拆分效率又不会因为单次传输太长导致远端缓冲区溢出。同时在接收端要做好流控预留足够大的接收缓冲区避免对端发太快导致数据覆盖。5.4 SRIO 读写性能实测参考这里分享一组我们在 66781GHz FPGA 联调时的实测数据供参考。链路配置是 x4 5 GbaudSRIO 时钟 312.5MHzDDR3 运行在 1600MT/s。测试场景传输大小实测带宽备注DSP - FPGA NWRITE256KB约 1.9 GB/s纯数据搬运无中断DSP - FPGA NWRITE Doorbell1MB约 1.6 GB/s每 4KB 发一次 DoorbellFPGA - DSP NWRITE256KB约 1.8 GB/sFPGA 端主动写FPGA - DSP NWRITE Doorbell1MB约 1.5 GB/sDoorbell 处理开销占比上升可以看到加入 Doorbell 中断后会有一部分带宽损失因为每次中断都有上下文切换和处理开销。如果对带宽要求极高可以考虑关闭 Doorbell 中断改用轮询方式检查数据块的完成标志。当然这会增加 CPU 占用属于一种 trade-off。6. 实际调试经验链路协商失败、中断不触发等典型问题排查链路这部分内容是这套源码使用过程中最容易卡住的地方我把实际调试中的问题排查思路完整写出来。6.1 链路一直协商不上的排查顺序SRIO 链路协商Link Training失败是最常见的问题而且表现形式很多样端口状态寄存器停在UNINITIALIZED、对端设备 ID 读不到、LSU 发起的事务没有响应。我的排查顺序是这样的第一步读端口状态寄存器。6678 每个 SRIO 端口都有PORT_N_CTL和PORT_N_STAT寄存器。如果PORT_N_STAT显示PORT_UNINIT说明物理层训练根本没开始如果显示PORT_ERR说明训练失败了如果显示PORT_OK说明链路是好的。第二步检查参考时钟和 PLL。前面说了SRIO 参考时钟频率不对PLL 锁不上链路是不可能起来的。读PER_SET_CNTL的 PLL 锁定状态位如果没锁先查硬件时钟。第三步核对对端设备配置。这里最容易出问题是速率不匹配。比如对端 FPGA 只支持 3.125 Gbaud而 6678 这边配了 5 Gbaud协商失败。SRIO 协议里有一个协商机制双方会交换能力集但如果一方配置成固定速率、不做降速协商链路就直接失败。所以建议在配置里使能自动协商如果对端支持或者干脆把双方速率都固定在同一个值。第四步用维护包读对端设备信息。SRIO 协议里有维护包Maintenance Packet可以读对端的 Device ID、Vendor ID 等信息。如果维护包能通说明链路物理层已经 OK 了问题在更高层。如果维护包都不通那就老老实实查物理层。6.2 Doorbell 中断触发不了一种隐蔽的原因之前调试联调时遇到一个隐蔽问题Doorbell 中断偶尔触发、偶尔不触发。后来发现原因是Doorbell 的优先级配置。在 SRIO 模块中Doorbell 事务和 NWRITE 数据包在 SRIO 端口的发送缓冲里有不同的优先级。如果数据流量很大把发送缓冲占满了Doorbell 包可能会在缓冲里排队延迟增大。如果 Doorbell 的优先级配置比 NWRITE 低在拥塞时 Doorbell 甚至会大量丢弃。这里建议把 Doorbell 事务的优先级配置为最高确保它在紧急情况下能优先发出去。具体配置在LSU_Reg1的PRIO字段或者门铃发送相关的寄存器里。代码里可能会有一个#define DOORBELL_PRIORITY 0x3这样的宏定义0x3 就是最高优先级。6.3 FPGA 联调中的寄存器端序问题SRIO 协议规定数据按大端序传输而 6678 和大多数处理器是小端序。如果在 FPGA 端没有做字节序转换看到的 32 位数据可能就是反的。这是和 FPGA 联调时最容易扯皮的问题之一。在 6678 的 SRIO 模块中有一个PER_SET_CNTL寄存器的ENDIAN位可以配置 SRIO 端口上的字节序。代码里通常会把这个位配成 little-endian这样与 DSP 内存里的字节序一致。联调前一定先确认两端都配成相同的字节序模式或者约定好大小端转换方式。我们项目里因为这个问题波形数据解析出来全是错的后来通过在 FPGA 端把字节序手动转换才解决。6.4 LSU 操作无响应的超时机制SRIO 有一个超时机制如果 LSU 发起了一个事务比如 NREAD对端没有在超时时间内响应硬件会返回一个错误。6678 SRIO 模块的 LSU 支持超时重发配置在LSU_Reg1的RTSVRead/Write Transaction Stalled Virtual Channel字段或TIMEOUT字段配置。重点提醒不要低估 NREAD 的超时问题。NREAD 是需要响应的操作对端响应慢可能导致 DSP 这边 LSU 一直挂起等待。如果对端是 FPGAFPGA 逻辑里处理 NREAD 请求的响应时间如果不确定可能会触发 LSU 超时进而影响整个 SRIO 传输链路。我的建议是在初始化时合理配置 LSU 超时值同时在错误中断处理里做好相关状态位的清除。7. 从这套代码到实际产品移植和改造需要注意的几个方向这套源码跑通了 EVM 板但离产品落地还有距离。如果在实际产品中要使用个人经验有这几个改造方向。7.1 基于 LLD 替代裸寄存器操作如果项目工期紧、对 SRIO 不熟悉建议直接用 TI 官方 PDK 里的 Srio LLD 驱动而不是在这套裸寄存器代码上开发业务功能。LLD 的优势在于封装了底层寄存器操作API 更稳定支持多核 SMP 架构下的资源共享对中断、DMA 的描述符管理有统一的抽象有 TI 官方维护和更新但这套代码依然有很强的学习价值甚至我的建议是先用裸寄存器代码跑通链路、理解寄存器行为再切换到 LLD 做业务开发。不然直接上手 LLD很多错误背后的原理理解不了出了问题只会看错误码排查效率很低。7.2 多 Buffer 环形队列管理这套源码里的收发缓冲是简单的静态数组测试足够但产品场景下建议改成环形队列Ring Buffer管理。数据持续高速涌入时如果只有单一缓冲区DSP 处理完一批数据前下一批数据就可能覆盖缓冲区产生数据完整性风险。改成环形队列后可以做到多个缓冲轮流用一边 DMA 写入一边 DSP 处理互不干扰。具体实现上可以在接收侧维护一个描述符队列每次 SRIO 写完一个缓冲区Doorbell 中断处理函数就把这个缓冲区挂到待处理队列DSP 核心在空闲时取队列里的缓冲区处理。这是典型的生产者-消费者模式能显著提升处理效率。7.3 多核场景下的数据分发C6678 有 8 个核如果只有核 0 处理 SRIO 数据其他核就闲着了。一个更合理的架构是核 0 作为 SRIO 数据接收核心收到数据后通过 IPCInter-Processor Communication将数据分发到其他核处理。或者也可以让多个核各自配置一个 LSU 单元并行向不同的目标设备发起读写。这套源码的main.c里没有涉及多核协同但理解 SRIO 的数据通路后扩展多核应用只是加一个 IPC 层的问题。实际操作中建议用 TI 的 IPC 组件比如 MessageQ来实现核间通信代码可维护性会好很多。7.4 容错处理与错误监控产品化之后SRIO 链路的稳定性直接关系到整个系统的可靠性。建议在业务代码中增加链路监控周期性地读PORT_N_STAT寄存器确认链路状态仍为PORT_OK配置错误中断并把错误状态打印出来如果链路断开尝试重新初始化端口还有一点容易被忽略SRIO 的 PSPower State切换。有时候 FPGA 升级配置或者复位重启SRIO 链路会断开DSP 这边如果没做链路恢复逻辑就必须整板重启才能恢复。我在产品上加了链路失步检测和重训练逻辑系统健壮性得到了明显提升。8. 关于这个压缩包再补充几句题外话最后说说这套源码本身的代码风格和可维护性。从压缩包内容和命名习惯来看6678_srio_program应该是国内工程师或高校实验室整理的代码风格偏能用就行变量命名比较简单函数注释也不算齐全。如果你拿到后要做二次开发建议先做几件事把所有打印信息统一规范化加时间戳。把关键寄存器的读写封装成带错误检查的宏或函数。在工程里加入版本号管理方便后期维护。这套源码虽然不如 TI 官方 MCSDK 例程那么规范但它把 SRIO 的完整流程串起来了上电初始化、链路配置、LSU 读写、Doorbell 中断、Cache 操作省去了自己从零啃协议栈和寄存器手册的过程。作为学习材料或者第一版参考实现价值很高直接用于产品则需要补充很多工程化的工作。从我个人的项目经历来看SRIO 这套东西看着寄存器多、概念杂其实只要你把地址映射、数据传输、中断通知、Cache 一致性这四条主线抓住剩下的都是细枝末节。调试手段也很重要TI 的 CCS 里可以直接查看内存和寄存器配合抓包工具比如 RapidIO 协议分析仪能快速定位链路层问题。如果只是 FPGA 和 DSP 联调没有协议分析仪用维护包读设备 ID 也是一个非常有效的排查手段。如果你正在拿这套源码做开发遇到什么奇怪的问题欢迎在评论区留言讨论。SRIO 的坑比较隐蔽有时候一个字节序、一个寄存器字段就能让你折腾一整天多交流能少走很多弯路。本文还有配套的精品资源点击获取
返回列表