第四章:NPU微架构入门:计算单元(MAC阵列)、控制单元、存储单元(SRAM/Scratchpad)的基本概念
好,我们进入正题。前面几章聊了指令集的设计哲学,这一章咱们得落地了——看看这些指令到底在芯片里是怎么跑起来的。
NPU的微架构,说白了就是三大块:算的、管的、存的。也就是计算单元、控制单元、存储单元。我当年刚接触NPU时,总觉得这玩意儿很神秘,后来拆开一看,嗯,其实核心逻辑并不复杂。
4.1 计算单元:MAC阵列——NPU的心脏
计算单元,尤其是MAC(乘加)阵列,是NPU最核心的部分。你想想看,神经网络里90%以上的运算都是矩阵乘法和卷积,而这些运算归根结底就是一堆乘法和加法。
MAC阵列的基本结构
一个MAC单元,就是做一次乘加操作:d = a × b + c。多个MAC单元排列成阵列,就能并行处理大量数据。
举个例子,一个4×4的MAC阵列:
// 伪代码:4x4 MAC阵列的一次运算
for (i = 0; i < 4; i++) {
for (j = 0; j < 4; j++) {
C[i][j] = A[i][0]*B[0][j] + A[i][1]*B[1][j] + ... + A[i][3]*B[3][j];
}
}
这其实就是矩阵乘法的硬件实现。我在项目中遇到过一个问题:阵列大小怎么定?
关键参数:MAC阵列规模
- 小阵列(8×8或16×16):适合低功耗、边缘设备,面积小,但吞吐量有限
- 中阵列(32×32或64×64):主流选择,平衡性能和功耗
- 大阵列(128×128以上):云端NPU,算力猛,但功耗和面积也大
我个人习惯,先看目标应用。如果是手机NPU,32×32起步;如果是自动驾驶芯片,至少64×64。别一上来就追求最大,流片失败的成本你扛不住。
MAC阵列的数据流
数据怎么喂给MAC阵列?主要有三种方式:
| 数据流方式 | 特点 | 典型应用 |
|---|---|---|
| 权重固定(Weight Stationary) | 权重数据留在MAC内,输入数据流动 | 卷积层,权重复用率高 |
| 输入固定(Input Stationary) | 输入数据留在MAC内,权重流动 | 全连接层,输入复用率高 |
| 输出固定(Output Stationary) | 部分和留在MAC内,减少写回 | 深度可分离卷积 |
小提示:实际设计中,我建议用混合数据流。比如卷积层用权重固定,全连接层用输入固定。别死板,灵活点。
4.2 控制单元:NPU的“大脑”
控制单元负责指令译码、数据调度、状态管理。它不像MAC阵列那样吃算力,但设计不好,整个NPU都会卡住。
控制单元的核心模块
- 指令译码器:把咱们之前设计的指令,翻译成控制信号。比如一条
MAC R1, R2, R3,译码器要告诉MAC阵列:R1和R2是输入,R3是输出。 - 地址生成器:计算数据在存储单元中的地址。我见过一个坑:地址生成器没考虑对齐,结果读出来的数据全是错的。
- 状态机:管理NPU的运行状态——空闲、取指、译码、执行、写回。嗯,这里要注意,状态机设计要防死锁。
控制单元的流水线
NPU控制单元通常采用多级流水线。我习惯用5级流水:
// 5级流水线示意
Stage 1: IF (取指) - 从指令存储器读取指令
Stage 2: ID (译码) - 解析指令,生成控制信号
Stage 3: EX (执行) - MAC阵列运算
Stage 4: MEM (访存) - 读写数据存储器
Stage 5: WB (写回) - 结果写回寄存器
为什么会用5级?因为MAC运算本身就需要多个时钟周期,流水线能提高吞吐量。但要注意,流水线越深,冒险问题越严重。
避坑指南:我曾经设计过一个8级流水线的控制单元,结果数据冒险和结构冒险搞得我焦头烂额。后来我学乖了:流水线深度不是越深越好,够用就行。对于NPU,4-6级是比较合理的范围。
4.3 存储单元:SRAM与Scratchpad
存储单元是NPU的“粮仓”。数据喂不饱MAC阵列,再强的算力也是白搭。
SRAM vs. Scratchpad
很多人搞不清这两个概念。我简单解释一下:
- SRAM(静态随机存取存储器):片上缓存,速度快,但面积大。通常用作L1/L2缓存。
- Scratchpad(便签存储器):也是片上存储,但由软件直接管理,没有硬件缓存一致性逻辑。
你想想看,SRAM是硬件自动管理的,你只管读写,缓存一致性由硬件保证。Scratchpad呢,你得自己管——数据什么时候搬进来,什么时候写回去,全由软件控制。
存储层次设计
NPU的存储层次一般是这样:
| 层级 | 容量 | 延迟 | 用途 |
|---|---|---|---|
| 寄存器文件 | 几百字节 | 1个周期 | MAC阵列的临时数据 |
| Scratchpad | 几十KB到几MB | 2-4个周期 | 权重、输入特征图 |
| SRAM(L2缓存) | 几MB | 10-20个周期 | 中间结果、大块数据 |
| DRAM(片外) | 几GB | 几百个周期 | 模型参数、数据集 |
重点:NPU的性能瓶颈往往不在MAC阵列,而在存储带宽。我见过一个项目,MAC阵列利用率只有30%,就是因为数据搬不过来。所以,存储带宽设计比算力设计更重要。
Scratchpad的软件管理
用Scratchpad,你得写DMA(直接存储器访问)代码。比如:
// 伪代码:从DRAM搬运权重到Scratchpad
DMA_Config config;
config.src_addr = DRAM_BASE + weight_offset;
config.dst_addr = SCRATCHPAD_BASE;
config.size = 256 * 1024; // 256KB
config.direction = DMA_DRAM_TO_SCRATCHPAD;
DMA_Start(&config);
// 等待DMA完成
while (!DMA_IsDone()) {
// 可以干点别的,比如处理上一批数据
}
嗯,这里要注意:DMA传输和MAC计算要尽量重叠,别让MAC闲着等数据。
4.4 三大单元的协同工作
计算、控制、存储,这三者怎么配合?我画个简单的流程:
- 控制单元从指令存储器取指令,译码后生成控制信号
- 控制单元通知存储单元:把数据从DRAM搬到Scratchpad
- 控制单元通知计算单元:从Scratchpad读取数据,开始MAC运算
- 计算单元运算完成后,结果写回存储单元
- 控制单元检查是否还有下一条指令,有则重复
说白了,控制单元就是“项目经理”,计算单元是“干活的人”,存储单元是“仓库”。项目经理指挥仓库备料,然后让工人干活,干完再把成品存回仓库。
我的经验:设计微架构时,先画数据流图,再画控制流图。数据流决定性能上限,控制流决定能否达到这个上限。很多新手一上来就画电路,结果数据流不通,白费功夫。
4.5 本章小结
这一章我们聊了NPU微架构的三大单元:
- 计算单元(MAC阵列):NPU的心脏,决定了算力上限。阵列规模、数据流方式要仔细权衡。
- 控制单元:NPU的大脑,负责指令译码、数据调度。流水线设计要防冒险。
- 存储单元(SRAM/Scratchpad):NPU的粮仓,带宽往往比算力更关键。Scratchpad需要软件管理,灵活性高但编程复杂。
下一章,我们会深入MAC阵列的微架构设计,包括脉动阵列、树形加法器等具体实现。到时候我会分享一些我在实际项目中踩过的坑,嗯,保证让你少走弯路。