2. C7x 架构与指令集精要
C7x 是 TI Jacinto 7 系列 SoC 中承担视觉与深度学习加速任务的核心 DSP。它并非传统意义上只做乘加运算的定点 DSP,而是一套由标量控制、矢量并行、矩阵加速、流引擎和分层存储共同构成的异构计算单元。理解 C7x 的关键,不是记住某个指令的编码,而是建立"数据从哪来、由谁算、算完放哪、如何被下一级消费"的整体图景。本节围绕这一图景展开。
一、四类运算单元的分工
C7x 的执行资源可归纳为四类。第一类是 标量单元(Scalar),负责地址计算、循环控制、分支判断和标量算术,是整个内核的"指挥中枢"。第二类是 矢量单元(Vector),C7x 提供 512 位宽的向量寄存器(如 V0–V31),单条指令可同时处理 16 个 32 位整数或 64 个 8 位整数,是图像滤波、色彩转换、特征提取的主力。第三类是 矩阵乘法加速器(MMA),面向深度学习中的卷积与全连接,以脉动阵列方式在单周期内完成大规模乘累加,显著高于用矢量指令逐条模拟的效率。第四类是 流引擎(Streaming Engine, SE),它不执行算术,而是负责在 L2、L1D、向量寄存器之间搬运数据块,并支持地址生成与格式转换,使计算单元不必为取数停顿。
这四者的关系可以概括为:标量发令、矢量并行、MMA 密集、流引擎供数。任何一段高性能 kernel 都是它们协同的结果,而不是单一单元的独角戏。
二、内存层次与数据流
C7x 的存储层次自上而下为:外部 DDR → MSMC/L3 → L2 SRAM → L1D SRAM → 向量寄存器。越靠近计算单元,容量越小、带宽越高、延迟越低。C7x 的 L1D 通常为 32 KB,L2 为 512 KB 量级(具体以器件手册为准),因此大尺寸特征图必须分块(tiling)处理,让每一块在被换出前尽可能被复用。
流引擎在这里扮演关键角色:它通过 SE 指令描述一次数据搬运的源地址、目标地址、步长和块大小,硬件自动完成循环搬运,无需标量逐次寻址。这既降低了指令开销,也让数据预取与计算重叠。下图给出 C7x 的运算单元与存储层次关系,以及一次典型卷积块的数据流向。
三、编程模型:从 C 到内联指令
C7x 的编程模型建立在 C7000 编译器之上。开发者通常先用 C/C++ 描述算法,再通过 #pragma 与内联函数(intrinsics)引导编译器生成矢量或 MMA 指令。核心手段有三:
- 向量化:使用
__vector类型或c7x_前缀的内联函数,让编译器把循环展开为 512 位操作。 - 数据分块:将大张量切成能放进 L1D 的小块,配合流引擎预取,隐藏 DDR 延迟。
- MMA 调用:对卷积层,将权重与激活按矩阵分块,调用 MMA 内联函数完成
C = A×B + C的批量计算。
一个典型的矢量加法示例可以写成:
// 假设 a、b、c 均为 32 位整数数组,长度 N 为 16 的倍数
// 使用 C7x 矢量内联函数完成逐元素相加
#include <c7x.h>
void vec_add(const int32_t *a, const int32_t *b, int32_t *c, int n) {
for (int i = 0; i < n; i += 16) {
// 一次加载 16 个 32 位整数到向量寄存器
__vector int32_t va = *(const __vector int32_t *)(a + i);
__vector int32_t vb = *(const __vector int32_t *)(b + i);
__vector int32_t vc = va + vb; // 单条矢量加法指令
*(__vector int32_t *)(c + i) = vc;
}
}
这段代码在 C7x 上会被编译为若干条 512 位加载、加法和存储指令,相比标量循环,理论吞吐提升约 16 倍。实际性能还取决于数据是否已在 L1D、是否发生 bank 冲突,以及循环是否被软件流水化。
四、参数与性能判断
评估一段 C7x kernel 是否达到预期,可关注几个可观测指标:单次迭代的周期数(通过 TSCL 或 TSCH 寄存器读取时间戳)、L1D 命中率、流引擎搬运与计算的重叠程度。若发现计算单元利用率低,常见原因是数据未预取、分块过大导致 L1D 颠簸,或 MMA 的矩阵维度未对齐到硬件要求(如 8×8 或 16×16 的倍数)。