3. C6000系列DSP架构基础:CPU内核、内存与总线

做DSP算法部署,说白了就是跟芯片架构打交道。你算法写得再漂亮,跑在硬件上不匹配,照样白搭。C6000系列是TI的旗舰DSP,我这些年跟它打交道最多,踩过的坑也不少。今天咱们就把它的底裤扒开看看——CPU内核怎么干活、内存怎么分层、外设怎么搬数据。

3.1 CPU内核架构:到底谁在干活?

C6000系列的核心是VLIW架构,也就是超长指令字。什么意思?就是一条指令里塞了好几个操作,让CPU一个时钟周期干多件事。我刚开始接触时总觉得这玩意儿玄乎,后来调了几个循环才发现,编译器才是真正的幕后英雄。

核心要点:C6000的CPU内核由多个功能单元并行工作。以C674x为例,它有8个功能单元——2个乘法器(.M1/.M2)、2个算术逻辑单元(.L1/.L2)、2个移位/分支单元(.S1/.S2)、2个数据加载/存储单元(.D1/.D2)。

你想想看,一个时钟周期里,这8个单元可以同时干活。比如.M1做乘法,.L1做加法,.D1从内存取数,.S1做跳转——互不干扰。这就是VLIW的精髓。

但有个坑:指令打包。编译器得把不冲突的操作塞进同一个指令包。如果操作之间有数据依赖,那就只能串行。我曾经在一个音频算法里,因为没处理好循环展开,导致功能单元利用率不到30%。后来手动调整了数据流,才把效率拉到70%以上。

功能单元 数量 主要职责 我踩过的坑
.M1/.M2 2 定点/浮点乘法 乘法器不够用时,编译器会拆成多次加法,性能暴跌
.L1/.L2 2 算术/逻辑运算 饱和运算用.L单元最快,别用.D单元绕路
.S1/.S2 2 移位、分支、常数生成 循环跳转尽量用.S2,别跟.S1抢资源
.D1/.D2 2 内存加载/存储 双通道加载时,地址要对齐,否则多花一个周期

我的习惯:写循环体之前,先画个数据流图,标出哪些操作可以并行。然后手动给编译器加pragma hint,比如#pragma MUST_ITERATE(4)告诉编译器循环次数是4的倍数,方便它做软件流水。

3.2 寄存器文件:CPU的临时工

C6000有两组寄存器文件——A组和B组,每组32个32位寄存器。A组连.M1/.L1/.S1/.D1,B组连.M2/.L2/.S2/.D2。跨组访问?可以,但要多花一个周期。

我记得有个项目做实时FFT,数据在A组寄存器里,但乘法器.M2在B组。每次乘法前都得把数据从A搬到B,白白浪费了十几个周期。后来我把数据分配改成A组给.M1,B组给.M2,性能直接翻倍。

注意:寄存器分配是编译器的活,但你可以用register关键字或内联汇编干预。别全交给编译器,尤其是循环体内的关键变量,手动指定寄存器组能省不少事。

3.3 内存层次结构:L1/L2/L3,谁快谁慢?

DSP的内存跟PC不一样。PC有缓存,DSP也有,但DSP的缓存可以配置成SRAM。说白了,你可以把一部分缓存当成普通内存用,保证访问延迟固定。这对实时算法太重要了。

C6000的内存分三层:

  • L1:离CPU最近,分L1P(程序)和L1D(数据),各32KB。可以配成缓存或SRAM。访问延迟1个周期。
  • L2:256KB或512KB,统一存储。可以配成缓存、SRAM或混合。访问延迟5-8个周期。
  • L3:片外DDR2/DDR3,几百MB到几GB。访问延迟几十到上百个周期。

你想想看,如果算法频繁访问片外DDR,性能肯定拉胯。我做过一个图像处理项目,一开始数据全放DDR,帧率只有15fps。后来把关键查找表搬到L2 SRAM,帧率直接飙到60fps。

黄金法则:热数据放L1,温数据放L2,冷数据放DDR。循环体内的数组、系数表、查找表,必须放在L1或L2 SRAM里。

下面这张图是我自己总结的内存层次结构,你一看就明白:

C6000内存层次结构 C6000内存层次结构 CPU内核 L1 缓存/SRAM (32KB L1P + 32KB L1D) 延迟:1周期 | 可配置为缓存或SRAM L2 缓存/SRAM (256KB ~ 512KB) 延迟:5~8周期 | 统一存储,可混合配置 L3 片外DDR2/DDR3 (几百MB ~ 几GB) 延迟:几十~上百周期 | 容量大但慢 💡 我的经验: 热数据(循环体、系数表)→ L1 SRAM;温数据(中间结果、大数组)→ L2 SRAM;冷数据(原始输入、最终输出)→ DDR。

3.4 外设总线:EDMA、McBSP、EMIF

CPU再快,数据搬不进来也是白搭。C6000的外设总线就是干这个的。我重点说三个最常用的:

3.4.1 EDMA:数据搬运工

EDMA(增强型直接内存访问)是DSP的DMA控制器。它可以在不占用CPU的情况下,在内存和外设之间搬数据。说白了,CPU只管算,EDMA只管搬,互不干扰。

EDMA有几个关键参数:

  • 通道数:C674x有64个EDMA通道,可以同时处理64个独立的数据流。
  • 传输粒度:支持1D(一维数组)和2D(二维矩阵)传输。做图像处理时,2D传输能省不少事。
  • 链接传输:一个传输完成后自动触发下一个,形成链式搬运。

我的习惯:用EDMA做ping-pong缓冲。两个缓冲区,一个给CPU算,一个让EDMA搬。算完交换,无缝衔接。我曾经用这个技巧把音频处理延迟从5ms降到0.5ms。

3.4.2 McBSP:串口老将

McBSP(多通道缓冲串行端口)是TI的老牌串口,专门用来接音频编解码器、调制解调器这些设备。它支持时分复用,可以同时处理多个通道的数据。

我记得有个项目用McBSP接音频ADC,采样率48kHz,16位立体声。一开始数据老是丢帧,后来发现是McBSP的时钟极性配反了。调了PCR寄存器里的时钟极性位,问题解决。

外设 典型用途 配置要点 避坑指南
EDMA 大数据搬运、ping-pong缓冲 通道优先级、传输粒度、链接模式 我曾经忘了配OPT寄存器里的中断使能位,导致传输完成没通知CPU
McBSP 音频、串行通信 时钟极性、帧同步、数据位宽 时钟极性配反,数据全错位,查了两天
EMIF 接外部存储器(SRAM、Flash) 时序参数、总线宽度、片选信号 EMIF时序配太紧,高温下数据出错,留20%余量才稳

3.4.3 EMIF:外部存储器接口

EMIF(外部存储器接口)用来接片外的SRAM、Flash、FPGA等设备。C6000的EMIF支持异步和同步两种模式。异步模式简单,但速度慢;同步模式快,但时序要求高。

我曾经在EMIF上接了一个FPGA,做实时数据采集。FPGA往EMIF写数据,DSP从EMIF读。一开始时序没调好,读出来的数据全是乱的。后来用示波器量了EMIF的读写时序,发现建立时间不够,把ASETUP参数从1改成3,问题解决。

注意:EMIF的时序参数一定要留余量。芯片手册上给的是典型值,实际板子走线、温度变化都会影响。我一般留20%的余量,高温下也能稳定跑。

3.5 总结:架构决定性能上限

C6000的架构,说白了就是一套分工明确的系统。CPU负责算,内存负责存,EDMA负责搬,外设负责接。你只要把数据流理顺,让每个部件干自己最擅长的事,性能自然就上去了。

我个人习惯,拿到一个新项目,先画数据流图,标出哪些数据在L1、哪些在L2、哪些走EDMA。然后对着架构手册,把每个部件的参数配好。这样调出来的代码,基本不会出大问题。

嗯,这一章就到这里。记住:架构是死的,人是活的。你理解了它,它就能为你所用。