第 33 环 · 起始块号可编程(自增从任意块起 · 大矩阵分段扫)
还是同一个圆心:一台开机只有一个 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] | 两条路径逐位一致(递增不推翻) |
单元仿真 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 权重」又近一层。
现在核还得为每一段写一次「起点 + 段长」、发一次 start。真芯片上段是成串的:一张段表摆在片内(第 1 段起几块、第 2 段起几块……),一次启动之后硬件自己把段表走完——段与段之间自动接续,核只发一声、只等一次完成。下一环:段表连扫 · 一次启动走完多段(段起始/段长硬件自读)。