🎯 圆心 · 目标(每环都从这里出发)
造一台 Salt OS 手机:基于 DeepSeek 的 AI 操作系统,iPhone 4S 外形(含底部圆形 Home 键),开机只有一个 AI(无桌面、无图标),软硬协同设计(OS + 芯片 + 主板一起做),为别人搭台、让别人在平台上赚钱。

🧭 完整框架(同心圆)

①文字闭环 → ②技术深入 → ③挂SRAM → ④权重INT4 → ⑤激活INT4 → ⑥NPU.STORE → ⑦KV Cache → ⑧注意力QK^T → ⑨Softmax → ⑩加权V → ⑪前馈FFN → ⑫残差·LayerNorm → ⑬多层堆叠 → ⑭QKV投影 → ⑮K/V分离 → ⑯自回归解码 → ⑰连续生成 → ⑱采样 → ⑲KV环形缓冲 → ⑳滑窗注意力 → ㉑多头注意力 → ㉒输出投影 W_o → ㉓多头块入残差流 → ㉔多头块进解码循环 → ㉕整层一块 → ㉖整网一块 → ㉗ISA契约 → ㉘权重内存化 → ㉙权重预取双缓冲 → ㉚权重分块 → ㉛矩阵行块 → ㉜行块自增 → ㉝起始块号可编程 → ㉞段表连扫 → ㉟段表流式取 → ㊱行块矩阵扫 ISA 化 → ㊲描述符链 → ㊳…

本环:一块矩阵是一条指令,那一层权重呢?

① 目标(这一环要解决什么)

上一环把「扫完一块矩阵」收成了一条 npu.rbmat:操作数在 rs1/rs2 里、忙闲一起等、结果线直接回来。可一层的权重不止一块矩阵——真 Transformer 一层里有 Wq/Wk/Wv/Wo、FFN 的 W1/W2、LN 的 gamma/beta,一层的权重是一串矩阵,每块各有自己的段表、自己的住处、自己该去的去处。

于是核又坐回了驾驶座:一块矩阵一条指令、一块一块往下发;块与块之间的「换矩阵、结果摆哪、要不要清累加器」仍是软件在管。硬件越强、驱动越长——这正是第 27 环给整网解掉的病,在「一层权重」这条路上又长回来了。这一环要把一整层权重也收进一条指令。

② 实现(做了什么)

做一条矩阵描述符链:内存里排一串矩阵描述符(一条就是一块矩阵的全部交代),核只给「链在哪、链多长」,硬件一条条走完——中间不发第二个 start、不读一次累加器、不写一个寄存器。

链条目(32bit)= { 标签[31:24] | 段数[23:16] | 段表基址[15:10]
                  | 权重基址[9:4] | 结果槽[3:2] | 保留 | 清累加器[0] }

每条描述符说清四件事:①这块矩阵住在哪(权重字基址——真芯片上就是内存里这块矩阵的地址)②它的段表在哪、有几段 ③算完把结果摆到哪个槽 ④算之前要不要清累加器。

硬件侧(rtl/row_blk.sv)在原来的段表扫描上加了三态:读链描述符(ST_CHND/ST_CHNW)→ 按条目里的段表把这块矩阵扫完(第 34/35 环那套连扫+流式取表一个字没改)→ 把累加器收口进结果槽(ST_SNAP)→ 翻下一条。四个结果槽 × 四个 lane 同时留在片上,核跑完一次性读走。多花的那一拍收口是必要的:最后一块的部分积在 ST_COMP 那一拍的边沿才落进累加器,此刻读到的才是完整的一行。

ISA 契约(核升 v9):npu.rbchain rs1, rs2(rs1=激活向量、rs2={链长[15:8], 链基址[5:0]})、npu.rbchain.s(段表走阻塞读,对照);npu.wait 一并覆盖链(min_bus 把链启动脉冲并进忙闲);npu.rd 选源再取一档 sel=7 = 走完的链条目数。新页 0x750~0x7AC(RB_CHNM/RB_CHNS/RB_CHNN/RB_CHN_GO + 探针 + 16 个槽寄存器 + SCREEN12),前 36 圈地址映射一字不改。

③ 怎么验证(硬证据)
证据结果说明
MMIO 开关全关,一条 npu.rbchain槽0=[-6,11,3,4]、槽1=[3,-2,5,-4]、槽2=[2,2,2,2]、槽3=[6,-11,-3,-4]四块矩阵各自的 W·x 一次到位(结果只可能来自指令通路)
npu.rd sel=7 vs MMIO RB_CHNC都是 4两条读回通路逐位一致:一条指令真走了四条
RB_CHNT(末条目标签)0xC3描述符真从内存读来——核没写过这个字段
改链条目 2 的权重基址(16→24)槽2 变、槽0 一个字不动描述符字段真管事(换的是这块矩阵的住处)
npu.rbchain.s(段表阻塞读)四个槽逐位一致取表方式由指令选,结果不变
老路径(MMIO 四寄存器 + RB_CHN_GO)四个槽逐位一致递增不推翻:两条来路同一套硬件
SCREEN12 = 0x04C3FAFC
链条目数 4 | 末条目标签 0xC3 | 槽0 lane0 0xFA(-6) | 槽3 lane3 0xFC(-4)

单元仿真 tb_row_blk37(35 个用例):四条目链 == 四条单跑、描述符字段(权重基址/段数)改哪个槽就变哪个、条目里的「清累加器」位管事(不清则累加器跨矩阵继续长,槽1 = 槽0 + 下一块的结果)、单条目链 == 第 34/35/36 环段表连扫老路径逐位一致、空链立刻完成不挂死(start 后那一拍 done 就拉高)、某条目段数为 0 时该槽被清成 0 且链继续往下走、开/关段表预取四个槽逐位一致。

ISA 单元仿真 tb_isa_rbchain 只验契约本身:四块矩阵与段表写进 SRAM,MMIO 开关全写 0,一条 npu.rbchain → 四个槽对表全中;改条目 2 的住处 → 只有槽 2 变;npu.rbchain.s 与流式取逐位一致;老路径逐位一致;全通 SCREEN = 0x0000C5C5。

协同仿真 tb_min_soc37:SCREEN = 0xF905FD04、SCREEN2~11 与第 25~36 环逐位一致(本环只加一条链通路,主通路一个字没动);采样序列仍是 [0,2,0,0,0,3]、第 6 步吐停止符 3 而停。全量回归 60 项全绿。

④ 调试中钉住的钉子

其一:报忙要报在第一拍。链启动脉冲在 EX 拍才置起来,引擎下一拍才看到 start、再下一拍 busy 才起来——核走 MMIO 轮询时会在引擎忙起来的前一拍把忙闲看成空、直接走人(与第 36 环 npu.wait 踩到的是同一颗钉子,只是这次出现在 MMIO 侧)。把链启动脉冲也算进 RB_BUSY 读回之后,忙闲首尾不断。

其二:换矩阵要把前瞻缓冲丢掉。第 35 环的段表预取会在扫描途中把「下一条表字」取进缓冲;一换矩阵,缓冲里那条就是上一块矩阵的表字。装载链条目时清掉缓冲(lk_valid/lk_req 归零),否则会拿着上块的表继续扫本块。

其三:老路径的清零在核手里,链的清零在描述符里。同一次「新矩阵乘」,MMIO 老路径靠核发 RB_CLR 脉冲,链路径靠条目里的清标志——这也是为什么条目要带这一位:一条指令里没有「先清再发」两个动作的位置。

⑤ 回到目标(这环让目标前进了多少)

从「一条指令扫一块矩阵」走到「一条指令扫完一整层权重」:一层的权重是一串矩阵,每块各有住处/段表/结果槽/清标志——这些交代全写进内存里的描述符链,核只给「链在哪、链多长」。整层的结论同时留在片上四个槽里,核跑完一次性读走,中间不发第二个启动、不读一次累加器、不写一个寄存器。

这正是软硬协同往深处走的样子:上一环把「一块矩阵的扫描」收进接口契约,这一环把「一层权重的调度」也收进去——核从「扫描的声明者」再退一步,成了「整层搬运的声明者」。真芯片上一层权重有几十上百块矩阵、每块几十上百段,软件看到的仍然只是一条指令。