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 寄存器组

各块的寄存器页(基址)[1][2]:

块 基址 块 基址
PC 0x0xxx PPU 0x6xxx
CNA 0x1xxx PPU-RDMA 0x7xxx
CORE 0x3xxx DDMA 0x8xxx
DPU 0x4xxx SDMA 0x9xxx
DPU-RDMA 0x5xxx 全局 0xf008
⚠️ 块基址是 IP 固有的,块内的偏移编码不是。RK3576 在同一个 CNA 基址上重排了

几何寄存器:同一个偏移的位打包不同,有些寄存器直接落在 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 深度 KtK_t。在输出 tile Mt=Nt=256M_t=N_t=256 时,KtK_t 随元素大小 反比变化[1]:

数据类型 元素字节 KtK_t @ Mt=Nt=256M_t{=}N_t{=}256
fp16 2 384
int8 1 768
int4 0.5 1536

实测规律是 Kt∝1/元素字节K_t \propto 1/\text{元素字节}[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 在片上相加, 于是回读量是 O(MN)O(MN) 而非 O(MN⋅nKt)O(MN\cdot n_{Kt})[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 上,各核偶尔会 比主机模型低 2−162^{-16},且触发输入集合各不相同;整数路径则与核无关[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

results matching ""

    No results matching ""