🎯 圆心 · 目标(每环都从这里出发)
造一台 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契约 → ㉘权重内存化 → ㉙权重预取双缓冲 → ㉚权重分块 → ㉛矩阵行块 → ㉜…

本环:切法不再写死——块号交给核,累加器跨块长大

① 目标是什么

还是同一个圆心:一台开机只有一个 AI 的手机。上一环已经能「一层权重分块流过片上缓冲」,可那个分块是写死的——第 0 块恒定是投影矩阵、第 1 块恒定是 FFN/LN,块号由位置决定,核没法说「我要算哪一块」。
真 NPU 上,权重是一个大矩阵(比如 4096×4096),要按矩阵行块切成很多块,由核(或 DMA)给一个可编程的块号;硬件自己去取那一块、算那一块的部分积,并把它累加进一个跨块保持的累加器——块块下来,累加器里才长出一整行输出。这一环把「分块」升级成「矩阵行块 + 可编程块号 + 跨块累加」。

② 怎么实现(不脱圆心)

· 矩阵按行切、按内维切:W(4×4 INT4)按行切 RB=2 行一块、按内维切 CB=2 列一块,共 NB = (D/RB)·(D/CB) = 4 块;
· 块号是可编程寄存器:新增页面寄存器 0x37C RB_BLK——核写块号,硬件把它译码成 (行基址 r0, 列基址 c0),只取那一块的行、只算那几行在那几列上的部分积;
· 累加器跨块累加:新增持久累加器 acc[0..3],不清零——同一行块的不同列块(块 0、块 1)会把部分积一段段加进同一行里;直到核写 0x380 RB_CLR 才归零,开始一次新的矩阵乘;
· 权重仍住内存:行块的行权重从权重 SRAM 取(w_addr = BASE + 块号·RB + 行内序号,放在字 32~39,不碰 0~31 字的层权重)——延续第 28 环「权重住内存」;
· 只扩不推翻:新引擎 row_blk.sv 与前 30 圈的引擎并列,主通路的整网(layer_net)一个字没动;新增 0x378~0x3B0 页,前 30 圈地址映射一字不改;
· 探针:0x3A0 RB_BLKO(块号回显)、0x3B0 RB_BASE(块号译出的行/列基址)、0x388~0x394 RB_ACC0..3(跨块累加器)、0x398 RB_PART0(本块部分积)。

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

新增 rtl/row_blk.sv(矩阵行块累加引擎)+ tb/tb_row_blk.sv(单元仿真);min_bus 挂第 31 环页 0x378~0x3B0 + 只读 0x3AC SCREEN6;circle31/gen_boot31.py 生成最小 OS(第 31 环试验段);tb/tb_min_soc31.sv 协同仿真。

// rtl/row_blk.sv(节选):块号 → (行基址, 列基址),部分积累加进跨块 acc
function [31:0] dec_rc(input [NB_W-1:0] b, input integer row_sel);
    integer t; begin
        t = b;
        if (row_sel != 0) dec_rc = (t / NCB) * RB;   // 行基址 r0
        else              dec_rc = (t % NCB) * CB;   // 列基址 c0
    end
endfunction

ST_COMP: begin
    for (n0 = 0; n0 < RB; n0 = n0 + 1) begin
        part_out[n0*ACC_W +: ACC_W] <= partial_c[n0];
        acc_out[(r0_i + n0)*ACC_W +: ACC_W] <=
            acc_out[(r0_i + n0)*ACC_W +: ACC_W] + partial_c[n0];  // 跨块累加
    end
    busy <= 1'b0; done <= 1'b1; st <= ST_IDLE;
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]。最小 OS 在芯片上真跑:

块号序列acc 累加器说明
RB_CLR + 块 0[-1, -2, 0, 0]只算行 0/1 的列 0/1 → 部分积
接块 1[-6, 11, 0, 0]同行块、另列块 → 累加成整行(-1→-6、-2→11)
接块 2、3[-6, 11, 3, 4]全 4 块跑完 == W·x;行 0/1 一路不变
RB_CLR + 2,3,0,1[-6, 11, 3, 4]换顺序结果逐位一致(块号可编程)
RB_CLR + 只跑块 1[-5, 13, 0, 0]块号一变,行/列与部分积全变
RB_CLR[0, 0, 0, 0]累加器可复位
SCREEN6 = 0x0402FFFA
总块数 4 | 块号译出基址 2 | 部分积 lane0 = 0xFF(-1) | 跨块累加后 lane0 = 0xFA(-6)
后两字节不同 = 「累加器真的跨块长大」的硬证据

单元仿真 tb_row_blk 7 组用例通过;协同仿真 tb_min_soc31:SCREEN = 0xF905FD04、SCREEN2 = 0x02040404、SCREEN3 = 0x04000204、SCREEN4 = 0x10200707、SCREEN5 = 0x04081807 与第 25~30 环逐位一致(本环只加了一个新引擎,主通路一个字没动);采样序列 [0,2,0,0,0,3]、第 6 步吐停止符 3 而停。

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

从「一层权重按位置切两块」走到「块号由核编程、按矩阵行块取、部分积跨块累加」——这正是真 NPU 把大矩阵切成成百上千块流式算完的那套机制:块号可编程 = 计算可以被调度;累加器跨块累加 = 一块块拼出整行输出。芯片离「跑真 DeepSeek 权重」又近了一层:层再大、矩阵再大,也只是块号一个个给、累加器一格格长。