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

本环:自增不再只从块 0 起——起始块号与段长都交给寄存器

① 目标是什么

还是同一个圆心:一台开机只有一个 AI 的手机。上一环把「谁在数块号」从软件搬进了硬件,但起点写死在块 0——一次启动永远从矩阵头扫到尾。
真芯片上一块矩阵按 2 行一块能有几百上千块(4096×4096 → 2048 块),核不可能一口气扫完,也常常只想扫其中一段(多核分块、流水线分段、只重算变化的那几段);更要紧的是——矩阵大到片上放不下时,一块「矩阵」本身就是分段搬进来算的,段与段之间累加器得接着长。这一环把「起点与长度」也做成寄存器:起始块号可编程、段长可编程、分段扫完累加器跨段保持。

② 怎么实现(不脱圆心)

· 起始块号:新增寄存器 0x3C0 RB_BLK0——自增扫描的起点,不再是写死的 0;
· 段长:新增寄存器 0x3C4 RB_SEG——本段要扫几块(写 0 = 扫到矩阵末尾);
· 跑法:clr 一次、发一段 RB_GO;硬件从 blk_start 自增着扫满 seg_n 块(或扫到矩阵末尾就停);累加器跨段保持——下一段接着往上累加,几段加起来 == 一次整扫;
· 回显探针:0x3C8 RB_BLKF(本段起始块号回显)、0x3CC RB_SEGC(本段扫过块数);RB_BLKC 仍数自 RB_CLR 以来的累计块数;
· 只扩不推翻:RB_AUTO=0 = 第 31 环老语义(核写块号、核发每块 start),一个字没删,两条路径必须逐位一致;RB_SEG=0 = 第 32 环老语义(从起点扫到矩阵末尾);前 32 圈地址映射一字不改;
· 位宽随矩阵长大:行/列基址回显从 2 bit 放宽到 4 bit(矩阵最多 16 行块),探针 0x3D0 SCREEN8。

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

改 rtl/row_blk.sv(新增 blk_start/seg_n 输入 + blk_first_out/segcnt_out 探针 + ST_IDLE 起点可编程 + ST_COMP 的「段没跑够就继续」判据),新增 tb/tb_row_blk33.sv(单元仿真,两个不同大小的矩阵);min_bus 挂 0x3C0 RB_BLK0 / 0x3C4 RB_SEG / 0x3C8 RB_BLKF / 0x3CC RB_SEGC / 0x3D0 SCREEN8 并新增第八观察点 screen8_out;circle33/gen_boot33.py 生成最小 OS(第 33 环试验段);tb/tb_min_soc33.sv 协同仿真。

// rtl/row_blk.sv(节选):起点可编程 + 段没跑够就接着自增
ST_IDLE: if (start) begin
    blk_start_r <= blk_start;      // 本段起点(核写)
    seg_r       <= seg_n;          // 本段块数(0 = 扫到末尾)
    segcnt_r    <= 8'd0;
    if (auto_mode) begin
        blk_r <= blk_start;        // 从任意块起(不再是写死的 0)
        blk_first_out <= blk_start;// 起点回显
        rbase_out <= st_r0; cbase_out <= st_c0;
    end
    ...
end

// 第 33 环:本段还没跑够吗
wire seg_more = more_blk &&
                ((seg_r == 8'd0) || ((segcnt_r + 8'd1) < seg_r));

ST_COMP: begin
    ...
    segcnt_out <= segcnt_r + 8'd1;         // 本段又扫过一块
    if (auto_r && seg_more) begin           // 段没跑够 → 自增续跑
        blk_r <= blk_r + 1'b1;
        st <= ST_FETCH;
    end else begin
        busy <= 1'b0; done <= 1'b1; st <= ST_IDLE;   // 本段扫完才放手
    end
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 在芯片上真跑(每段只说一次 start):

动作结果说明
clr + 第 1 段(起 0、2 块)acc = [-6, 11, 0, 0]只把行 0/1 长出来;RB_SEGC=2
接着第 2 段(起 2、2 块)acc = [-6, 11, 3, 4]跨段累加,两段 == 一次整扫
clr + 从块 1 起到末尾(段长 0)acc = [-5, 13, 3, 4]≠ 整扫的 -6 → 起始块号真起作用
clr + 段长 = 1(起 2)acc = [0, 0, -3, 8]RB_SEGC=1 → 段长只让扫一块
RB_AUTO=0 老路径[-6, 11, 3, 4]两条路径逐位一致(递增不推翻)
SCREEN8 = 0x0102FAFB
起始块号 1 | 本段扫过块数 2 | 第 1 段 lane0 = 0xFA(-6) | 任意起点段 lane0 = 0xFB(-5)
高字节 1 = 从块 1 起;次字节 2 = 这段就扫 2 块;后两字节不同 = 两段算了不同的东西

单元仿真 tb_row_blk33 用一个「大矩阵」实例把「分段扫」钉死:8×8 矩阵、16 块(D=8、NB=16),分 3 段 0..5 / 6..11 / 12..15 扫完,3 段累加与一次整扫逐位一致([-44,13,25,-44,13,25,-44,13]);从块 9 起扫到末尾 → [0,0,0,0,24,15,-44,13](大矩阵从中间起,只有后半行长出来)。协同仿真 tb_min_soc33:SCREEN = 0xF905FD04、SCREEN2 = 0x02040404、SCREEN3 = 0x04000204、SCREEN4 = 0x10200707、SCREEN5 = 0x04081807、SCREEN6 = 0x0402FFFA、SCREEN7 = 0x0403FA04 与第 32 环逐位一致(本环只给行块引擎加了两个可编程寄存器,主通路一个字没动);采样序列 [0,2,0,0,0,3]、第 6 步吐停止符 3 而停。

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

从「一次启动扫完整个矩阵」走到「一次启动扫完指定的一段;几段加起来就是一整块矩阵」——这正是软件与 NPU 分块的接缝:核不再需要知道矩阵有多长,它只说「从第几块起、扫几块」,硬件自己走完这一段、累加器自己接着长。矩阵大到片上放不下时,段就是搬运与计算的单位;多核分块、流水线分段也都长在这两个寄存器上。芯片离「跑真 DeepSeek 权重」又近一层。