CoolDA 设计仿真(一):CPU 如何把任务交给 NPU
前言
单独写一个 NPU 矩阵乘法模块,只能说明硬件会算。真正有系统意义的问题是:CPU 怎么使用它?
CoolDA 这类 SoC demo 要讲的就是这条路径:
1 | |
这篇先建立系统视角。后面再拆 APB 寄存器、BSP/runtime、仿真 shell 和测试契约。
为什么不是直接调用硬件函数
软件里我们很容易写出这样的幻想:
1 | |
但 CPU 不能真的“调用硬件函数”。硬件不是 C 函数,它通常暴露成一组寄存器。CPU 只能通过 load/store 访问某段地址,间接告诉硬件:
- 输入数据在哪里;
- 控制位怎么设置;
- 什么时候开始;
- 是否完成;
- 结果在哪里读。
因此,一个加速器平台至少需要三层软件:
| 层 | 作用 |
|---|---|
| runtime | 把高层任务组织成 job、tile、event |
| BSP driver | 把 job 的一小步翻译成寄存器读写 |
| shell/app | 给用户或测试提供入口 |
硬件则至少需要:
| 层 | 作用 |
|---|---|
| 总线接口 | 接收 CPU 的 MMIO 访问 |
| 寄存器文件 | 保存控制、状态、输入、输出 |
| 计算核 | 真正执行乘加 |
CoolDA 的价值是把这些层都连起来,而不是只展示其中某一层。
最小加速路径
一个最小路径可以这样理解:
1 | |
这条链路看似长,但每层都很薄。它把复杂问题拆开了:
- CPU 负责组织;
- runtime 负责调度;
- BSP 负责敲寄存器;
- NPU 负责固定小块计算。
为什么硬件核只做 4x4
教学级设计里,让硬件核固定为 4x4 有几个好处:
- 寄存器接口简单;
- 输入输出容易手算;
- 波形容易观察;
- 软件 tile 调度容易讲清楚;
- 不需要一上来引入 DMA、cache coherency、命令队列。
4x4 核的数学任务是:
1 | |
每个输出:
1 | |
输入是 INT8,输出是 INT32。这个设计非常适合说明“固定功能 accelerator kernel”。
SoC 里的 NPU 是一个 MMIO 外设
CPU 访问 CoolDA,不需要新指令。它像访问 UART、timer 一样访问某段地址。
可以把 NPU 想成一张寄存器表:
1 | |
CPU 往 A/B 寄存器写输入,往 CTRL 写 start,从 STATUS 读 done,再从 C 窗口读结果。
这就是最小 MMIO accelerator 的基本形态。
runtime 为什么不能省
如果只暴露 BSP,程序员每次都要手动:
- 打包 4x4 A;
- 打包 4x4 B;
- 写寄存器;
- 等硬件;
- 读 4x4 C;
- 自己累加到大矩阵。
runtime 的作用是把这堆动作变成更像“任务”的接口:
1 | |
有了 job,runtime 才能做 tile 调度、内存检查、同步/异步事件、测试和 benchmark。
当前边界
这个最小平台还不是商业 NPU SDK。它故意没有做很多东西:
- DMA;
- 命令队列;
- 中断完成;
- 多 stream;
- 后台硬件 async;
- accelerator-local SRAM;
- 大模型算子库。
但它已经把最关键的骨架打通:
- CPU 可以通过总线访问 NPU;
- BSP 可以驱动寄存器;
- runtime 可以把大任务拆成小 tile;
- shell 可以触发测试;
- simulator 可以观察整条链路。
这就是它作为教学平台的价值。
小结
CoolDA 这类 SoC demo 的核心不是“4x4 矩阵乘法多厉害”,而是展示一个加速器平台的最小闭环:
1 | |
下一篇进入硬件契约:APB 外设和 4x4 INT8 矩阵乘核到底长什么样。