RKNPU 硬件架构
本页介绍 RKNPU 的硬件结构:它是 NVDLA 的派生数据通路,一块固定的 CNA→CORE→DPU 卷积流水线,外加独立的 PPU 池化通路、每核的 CBUF、宽的定点/浮点 累加器,以及整芯片的三核复制。编程侧如何驱动这些块,见 编程架构。
1. NVDLA 血缘与块映射
RKNPU 的块、缓冲区、流水级与寄存器组织都来自 NVIDIA 开源的 NVDLA[1]。理解这一点,就能把大量"怪癖"解释清楚,也能知道哪些地方 NVDLA 文档适用、哪些不适用(Rockchip 追加的字段在 NVDLA 里查不到, 沉默不代表没有)[1]。
| NVDLA | RK 块 | 基址 | 说明 |
|---|---|---|---|
| CDMA(卷积 DMA) | CNA | 0x1000 |
RK 上合成一个块;NVDLA 在其内部再分 DC / WG / IMG / WT 引擎 |
| CSC(序列控制器) | CNA | 0x1000 |
特征 / 权重的加载器 |
| CBUF(卷积缓冲) | (由 CNA 配置) | — | RK 上从 CNA 0x1040 / 0x103c 配置,自身不占独立块 |
| CMAC + CACC | CORE | 0x3000 |
MAC 阵列 + 累加器 |
| SDP + SDP-RDMA | DPU + DPU-RDMA | 0x4000 / 0x5000 |
后处理;BS/BN/EW 对应 NVDLA 的 X1/X2/Y,LUT 对应其 LE/LO |
| PDP + PDP-RDMA | PPU + PPU-RDMA | 0x6000 / 0x7000 |
平面池化 |
| MCIF(内存接口) | DDMA | 0x8000 |
仲裁 / QoS / outstanding |
| SRAMIF(第二内存总线) | SDMA | 0x9000 |
通向片上 SRAM 的端口 |
| GLB | 全局 | 0xf008 |
每块一个 OPERATION_ENABLE |
| CDP(跨通道 LRN) | 缺失 | — | 没有通道轴数据通路 |
| BDMA(桥接 DMA) | 缺失 | — | 没有内存到内存拷贝引擎 |
| RUBIK(reshape) | 缺失 | — | 因此主机侧 de-tile 不可消除 |
| — | PC | 0x0000 |
Rockchip 自研:寄存器程序取指器,硬件层变成内存中的一段命令 |
三处"缺失"是有后果的负数:没有 CDP,特征轴归约只能用"全 1 权重"的矩阵乘来 表达;没有 BDMA,没有片上内存到内存的搬运;没有 RUBIK,布局变换留在主机侧[1]。
2. 固定流水线:CNA → CORE → DPU
数据通路是一段固定顺序:CNA→CORE→DPU,外加一条独立的 PPU 池化通路[1]。
| 块 | 角色 | 关键细节 |
|---|---|---|
| CNA | 把输入特征与权重从 DDR 搬进 CBUF,配置卷积几何 | 特征 / 权重的行、面 stride;权重解压引擎 cna_dcomp;反卷积模式 |
| CORE | MAC 阵列 + CACC 宽位累加 | 一个硬件层内跨 stripe 累加;无跨层累加 |
| DPU | SDP:BS / BN / EW 仿射级 + LUT + 输出转换 | 逐元素 add/mul(残差、门控);LUT 做 SiLU / GELU / exp 等;片上 requant |
| PPU | 平面池化 | max / average;按空间轴归约 |
流水线有几条硬约束:
- 块之间没有依赖跟踪。 两个待处理的硬件层不能互相阻塞;若后一个消费前一个的 输出,软件必须等生产者完成后再调度消费者[1]。
- DPU 让 NPU 不止会做 MAC。 EW 单元做逐元素加 / 乘(残差、门控激活),LUT 做 sigmoid / tanh / SiLU / GELU / sqrt / rsqrt / reciprocal / exp。与 matmul 和主机侧 的特征轴归约组合,足以在 NPU 上跑 RMSNorm、LayerNorm、softmax,乃至完整的 Transformer / Whisper encoder block[1]。
- PPU 不能做通道轴归约,特征轴归约只能借 CNA/CORE/DPU 的"全 1 矩阵乘"[1]。
3. 寄存器映射与 ping-pong 寄存器组
| 块 | 基址 | 块 | 基址 |
|---|---|---|---|
| PC | 0x0xxx |
PPU | 0x6xxx |
| CNA | 0x1xxx |
PPU-RDMA | 0x7xxx |
| CORE | 0x3xxx |
DDMA | 0x8xxx |
| DPU | 0x4xxx |
SDMA | 0x9xxx |
| DPU-RDMA | 0x5xxx |
全局 | 0xf008 |
几何寄存器:同一个偏移的位打包不同,有些寄存器直接落在 RK3588 的空位上。 因此换芯片需要逐芯片的 regcmd 编码器,而不是一张偏移表[2]。
NVDLA 的每个子单元都有乒乓寄存器组:两组可复制的"层"寄存器共享同一地址区间,
另有一组非影子寄存器放状态与指针。PRODUCER 选择 CPU 写入的组,CONSUMER
只读、表示数据通路正在取哪一组。硬件在层结束时清掉运行组的使能位、推进
CONSUMER,若另一组已使能则立即开始。往已使能组写会被静默丢弃——没有报错,
软件也无法清位[1]。
这套机制在 RK 上的映射是:
| NVDLA | RK 寄存器 |
|---|---|
POINTER.PRODUCER |
S_POINTER[0] |
POINTER.CONSUMER |
S_POINTER[16](只读 EXECUTER) |
D_OP_ENABLE |
OPERATION_ENABLE[0](OP_EN) |
每个块一个 S_POINTER:CNA 0x1004、CORE 0x3004、DPU 0x4004、PPU 0x6004[1]。
写 0xE(POINTER_PP_MODE | EXECUTER_PP_EN | POINTER_PP_EN)即可让硬件自动翻组。
kernel 在每个 task 前用 0xE | (0x10000000 * core_index) 武装 CNA 与 CORE,多出的
高位是每核一位,用户态的 regcmd 不得写这两个 S_POINTER[1][4]。这也是 PC
中断位成对出现(CNA_FEATURE_0/1、CORE_0/1…)的原因:后缀是寄存器组,
不是核[1]。
4. 片上存储:CBUF 与片上 SRAM
每个核有一块 CBUF(convolution buffer):12 banks × 32 KB = 384 KB[1]。 一次卷积 / 矩阵乘 task 里,CBUF 必须同时放下输入特征 tile 与权重 tile,这个约束 决定 K 方向 tile 深度 。在输出 tile 时, 随元素大小 反比变化[1]:
| 数据类型 | 元素字节 | @ |
|---|---|---|
| fp16 | 2 | 384 |
| int8 | 1 | 768 |
| int4 | 0.5 | 1536 |
实测规律是 [1]。CBUF 还有 operand reuse 位
(WEIGHT_REUSE / DATA_REUSE),让一个 task 复用上一 task 在同一核上已驻留的
tile,而不必从 DDR 重取[1]。一个坑:int8 特征 DMA 会多读一个 bank,所以
data_bank = ceil(bytes/bank) + 1;fp16 不受影响[1]。
除 CBUF 外还有约 956 KB 片上 SRAM,但走厂商独占路径
(CONFIG_ROCKCHIP_RKNPU_SRAM + debugfs),mainline 的 rocket 不暴露它[1]。
5. 累加器与数据类型
累加器是 NVDLA 形状的宽位累加器,在一个硬件层内跨 stripe 累加,层结束时舍入并 饱和到 INT32 / FP32[1]:
| 输入类型 | 累加器 | 输出 |
|---|---|---|
| int8 | INT34 | INT32 |
| int16 | INT48 | INT32 |
| fp16 | FP44 / FP48 | FP32 |
饱和 / 截断由 CORE_CLIP_TRUNCATE(0x301c)控制。没有跨层累加——这正是
K 分块回读的根因[1]。
数据类型菜单由 3-bit precision 字段独立选择输入 / MAC / 输出三级[1]:
| 类型 | precision | 输入位宽 | 累加输出 | 片上 K 累加 |
|---|---|---|---|---|
| int4 | 6 | 4 | int16 | 否 |
| int8 | 0 | 8 | int32 | 否 |
| int16 | 1 | 16 | int32(饱和) | 否 |
| fp16 | 2 | 16 | fp32 | 是 |
| bf16 | 3 | 16 | fp32 | 否 |
| tf32 | 7 | 32 | fp32 | 否 |
- fp16 是唯一有片上 K 累加的:DPU 的 EW 单元把 K 分块的 partial 在片上相加, 于是回读量是 而非 [1]。
- DPU 的片上 requant 用整数乘子 + 移位把 int32 累加器量化回 int8, BS 级按输出通道逐个缩放,因此 int8 卷积可以不做主机 requant[1]。
- 没有 host 侧布局转换寄存器:权重必须在主机预打散成 cube 布局;而 matmul 的 激活与输出可以只用 stride 保持行主序[1]。
6. 三核与 IOVA
RK3588 有 3 个 NPU 核(npu@fdab0000、fdac0000、fdad0000),可协同也可
独立,实测三核并发约有 3× 吞吐[1][3]。
- 在 mainline
rocket下,每个打开的 fd 是一个调度实体,各带 4 GB IOVA 窗口; 一个 fd 上的多个 job 串行落到同一个核。要用满三核,需开 3 个及以上 fd[1]。 - 每核的 fp16 计算并非完全一致:在每 16 通道分组的 lane 0 上,各核偶尔会 比主机模型低 ,且触发输入集合各不相同;整数路径则与核无关[1]。
7. 缺失的块及其后果
| 缺失 | 后果 |
|---|---|
| RUBIK(reshape) | 没有片上 layout 变换,主机侧 packing / de-tiling 不可消除 |
| CDP(通道轴) | 特征轴归约只能表达为"全 1 权重"的矩阵乘 |
| BDMA(桥接 DMA) | 没有内存到内存拷贝引擎 |
| gather / scatter / 索引读 | 数据通路只按固定 DMA 流式读连续 tile,没有索引取数;索引类算子是主机操作 |
其中"线性沿收缩轴"的操作(完整归约、加权归约、前缀和 cumsum)都可以写成对全 1 权重(可做三角)的矩阵乘,不需要新的 regcmd[1]。
参考文献
[1] GREGORDINARY. Rockchip NPU reverse-engineering notes[EB/OL]. (2026)[2026-10-09]. https://github.com/gregordinary/rockchip-npu-notes/tree/81b0d17da0b8a472c1a1e6f7d2ddba6b34d10111
[2] GREGORDINARY. rocket-userspace: include/npu_hw.h[EB/OL]. (2026)[2026-10-09]. https://github.com/gregordinary/rocket-userspace/blob/f86cf52c666b4eddadc17d80d6c558b4067c0d6b/include/npu_hw.h#L25-L33
[3] VIZOSO T. dt-bindings: npu: rockchip,rk3588-rknn-core.yaml[EB/OL]. Linux kernel, (2026)[2026-10-09]. https://github.com/torvalds/linux/blob/6c377d19d4a5116d9bec5203aa3c6c11523e7898/Documentation/devicetree/bindings/npu/rockchip,rk3588-rknn-core.yaml#L32
[4] Linux kernel. drivers/accel/rocket/rocket_job.c[EB/OL]. (2026)[2026-10-09]. https://github.com/torvalds/linux/blob/6c377d19d4a5116d9bec5203aa3c6c11523e7898/drivers/accel/rocket/rocket_job.c#L110-L140
© 2026 Yang Huan · yanghuan9812@qq.com