🎯 圆心 · 目标(每环都从这里出发)
造一台 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 化 → ㊲…

本环:硬件都做齐了,那就把它收回一条指令里

① 目标是什么

还是同一个圆心:一台开机只有一个 AI 的手机。第 31~35 环把「一块大矩阵怎么扫」全做进了硬件——块号可编程、块号自增、起始块号可编程、段表连扫、段表流式取,五层能力都在 row_blk 引擎里。
可核那边一点没省:扫一次矩阵,核还得一条条 sw 写 RB_X / RB_TBLM / RB_TBLS / RB_TBLN / RB_TBLP 五个寄存器,再 sw 一次 RB_GO,然后自己 lw 轮询 RB_BUSY,扫完还要逐 lane lw 读回 RB_ACC。硬件越做越强,软件驱动却越长——这正是第 27 环给整网解掉的病,在矩阵扫这条路上又长回来了。
本环把「扫完整块矩阵」也收进 ISA:核只说「激活在哪、段表在哪、扫几段」,剩下硬件自己做完;忙闲由 npu.wait 一起等,结果从 npu.rd 直接回来。

② 怎么实现(不脱圆心)

· 两条新指令(custom-0):funct3=011 npu.rbmat rs1, rs2(rs1=激活向量、rs2=段表描述符,段表走流式取)、funct3=100 npu.rbmat.s rs1, rs2(同上,但段表走阻塞读,留作对照);
· 描述符就在操作数里:rs2 = {段数[15:8], 段表基址[5:0]}——段表住哪、扫几段,一条指令说完;
· 一条指令内含三件事:清累加器(一次新的矩阵乘)+ 打开「段表连扫 + 块号自增」+ 选段表取法(流式/阻塞);
· 结果线收进 npu.rd:选源从 funct7[1:0] 放宽到 funct7[2:0],新增 4=行块累加器 lane0、5=按表扫过的段数、6=扫过的块数;
· 忙闲一起等:min_bus 把 rb_busy 并进核看到的 npu_busy——npu.wait 这条给整网写的冻结握手,原样覆盖矩阵扫(核不用再写轮询循环);
· 只扩不推翻:0x378~0x3FC 那批 MMIO 地址一个字没改,老路径(核写五寄存器 + RB_GO)与指令路径逐位一致;row_blk 引擎本身、SRAM 布局(层权重 0~31、矩阵行块 32~39、段表 40~42)全不动。

③ 本环做了什么(真代码)

改 rtl/riscv_core.sv(升 v8:新增 npu_rbmat/npu_rb_x/npu_rb_desc/npu_rb_pf 四根输出 + 三根读回线,npu.rd 选源放宽到 3bit),改 rtl/min_bus.sv(指令脉冲直连 row_blk 的配置寄存器与 go/clr,新增 0x63C SCREEN11 与第十一观察点 screen11_out),新增 tb/tb_isa_rbmat.sv(ISA 单元仿真,程序由 circle36/gen_isa36.py 生成)、circle36/gen_boot36.py(最小 OS 收尾⑮)、tb/tb_min_soc36.sv(协同仿真)。

// rtl/riscv_core.sv(节选):矩阵扫指令——操作数直接当启动脉冲送进引擎
// funct3=011 npu.rbmat   rs1=激活向量, rs2=段表描述符(流式取)
// funct3=100 npu.rbmat.s 同上,但段表走阻塞读(对照)
localparam [2:0] NPU_RBMAT  = 3'b011;
localparam [2:0] NPU_RBMATS = 3'b100;

assign npu_rbmat   = ex_npu_rbmat;   // 单拍启动
assign npu_rb_x    = ex_opA;         // rs1:激活向量
assign npu_rb_desc = ex_opB;         // rs2:{段数[15:8], 段表基址[5:0]}
assign npu_rb_pf   = ex_npu_rbpf;    // 流式取 / 阻塞读

// npu.rd 选源放宽到 funct7[2:0]:4=累加器 lane0, 5=段数, 6=块数
wire [31:0] npu_rd_data = (mem_npu_sel == 3'd0) ? npu_y         :
                          (mem_npu_sel == 3'd1) ? npu_idx       :
                          (mem_npu_sel == 3'd2) ? npu_slot      :
                          (mem_npu_sel == 3'd3) ? npu_kcnt      :
                          (mem_npu_sel == 3'd4) ? npu_rb_acc    :
                          (mem_npu_sel == 3'd5) ? npu_rb_tblcnt : npu_rb_blkcnt;

// rtl/min_bus.sv(节选):一条指令 = 五寄存器配置 + 清累加器 + 一条 start
if (core_npu_rbmat) begin
    rb_x         <= core_npu_rb_x;
    rb_tbls_r    <= core_npu_rb_desc[5:0];    // 段表基址
    rb_tbln_r    <= core_npu_rb_desc[15:8];   // 段数
    rb_auto_r    <= 1'b1;                     // 段内块号硬件自增
    rb_tblm_r    <= 1'b1;                     // 段表连扫
    rb_tblp_r    <= core_npu_rb_pf;           // 流式取 / 阻塞读
    rb_clr_pulse <= 1'b1;                     // 清累加器
    rb_go_pulse  <= 1'b1;                     // 就这一条 start
end
④ 怎么算成(闭环验证)

测试矩阵 W = [1,2,-1,0 / 0,1,1,-2 / -1,0,2,1 / 2,-1,0,1]、x = [3,-2,5,-4],手算 y = W·x = [-6,11,3,4]。段表 3 条(起 0 长 2 / 起 2 长 1 / 起 3 长 1),先把所有 MMIO 开关写 0(引擎的配置只剩指令这一条来路),最小 OS 在芯片上真跑:

动作结果说明
一条 npu.rbmat(段数 3)acc = [-6, 11, 3, 4]= W·x,结果只可能来自指令通路(MMIO 开关全关)
npu.wait 之后读 RB_BUSY0核没写过一个轮询循环:忙闲被同一条指令等掉了
npu.rd sel=4 / MMIO RB_ACC00xFA / 0xFA两条读回通路逐位一致
npu.rd sel=5 / sel=63 / 4段数、块数直接从结果线读回(核没写这两个寄存器)
描述符里写段数 = 2lane2 = -3、lane3 = 8整扫是 3 / 4 —— 第三条表没走,段数真是操作数
npu.rbmat.s(段表阻塞读)lane0 = -6与流式取逐位一致(取表方式由指令选)
改写表第 0 条(起 1 长 1)再跑lane0 = -5指令路径读的也是内存里的表(≠ 整扫的 -6)
老路径(MMIO 五寄存器 + RB_GO)acc = [-6, 11, 3, 4]与指令路径逐位一致——递增不推翻
SCREEN11 = 0xFAFA0304
npu.rd 读回 lane0 0xFA | MMIO 读回 lane0 0xFA | npu.rd 段数 3 | npu.rd 块数 4
高两字节都是 FA = 两条读回通路逐位一致;后两字节 = 段数、块数从结果线直接读回

单元仿真 tb_isa_rbmat 只验这道契约本身(不跑生成、不碰主路):矩阵权重与段表写进 SRAM,MMIO 开关全写 0,一条 npu.rbmat → 累加器 [-6,11,3,4];npu.rd 的三路读回与 MMIO 读回逐位比对;描述符里写段数 = 2 → 第三条表没走(lane2=-3 / lane3=8,整扫是 3 / 4);npu.rbmat.s(阻塞读表)逐位一致;全通 SCREEN = 0x0000A5A5。协同仿真 tb_min_soc36:SCREEN = 0xF905FD04、SCREEN2~10 与第 25~35 环逐位一致(本环只把「谁摆配置」从一串 sw 换成一条指令,主通路一个字没动);采样序列 [0,2,0,0,0,3]、第 6 步吐停止符 3 而停;全量回归 57 项全绿。

调试中钉住一处非显而易见的时序缝:npu.rbmat 在 EX 拍只把 rb_go_pulse 置起来,row_blk 要下一拍才看到 start、再下一拍 busy 才起来,而 npu.wait 恰好就是「下一拍」进 EX 的——不把 go_pulse 也算进忙闲,wait 会在引擎忙起来的前一拍把忙闲看成空、直接放行。改成 npu_busy = net_busy | rb_busy | rb_go_pulse 后忙闲首尾不断。

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

从「硬件会扫」走到「一条指令就能让硬件扫」:五个配置寄存器 + 一次启动 + 一个轮询循环 + 四次逐 lane 读回,收成 npu.rbmat + npu.wait + npu.rd 三条指令——操作数就在 rs1/rs2 里,忙闲一起等,结果线直接回来。核从「扫描的驱动者」也退成「扫描的声明者」:硬件的能力终于配上了同等简洁的软件接口。
这正是软硬协同的意思:前五环把能力往硬件里长,这一环把它们收进接口契约里——否则硬件越强、驱动越长,OS 迟早被自己的驱动拖住。真芯片上「一层权重分几十上百段搬进片上」这件事,现在在软件看来只是一条指令。