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里。
下面这张图是我自己总结的内存层次结构,你一看就明白:
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。然后对着架构手册,把每个部件的参数配好。这样调出来的代码,基本不会出大问题。
嗯,这一章就到这里。记住:架构是死的,人是活的。你理解了它,它就能为你所用。