🎯 圆心 · 目标(每环都从这里出发)
造一台 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 化 → ㊲描述符链 → ㊳链槽直喂 → ㊴主路走链 → ㊵链预投下层 → ㊶跨趟预投 → ㊷权重池三 bank → ㊸…

本环:链不再「投一层、歇一层」

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

第 40 环让链在算本层时投下层,第 41 环连「下一趟首层」也提前投好。可池子还是两个 bank:一个在算、一个在投,投完这一层,链就只能歇着等本层算完——因为再投就没有地方放了。

真跑生成时,一趟十几层、权重几百 MB:链取一层比算一层慢是常态。两 bank 的后果是链的利用率只有一半:投一层、停一层。要让它一直跑,池子必须能同时装下「本层 + 下两层」——预投深度从 1 提到 2。

② 实现(做了什么)

把双 bank 乒乓扩成三个 bank 的权重池,并把「投递」从「本层开头的一拍」改成连续泵:只要池子没满(下两层还没都在路上)、链又空着,就立刻接下一层,投完一层不用等。

两 bank(R41):[本层] [下层]  ← 投完下层,链空闲到本层算完
三 bank(R42):[本层] [下层] [下下层]
                 ↑ 本层开算时,链已经在投下下层(连续泵)

池子不再用「算出来」的 bank 号,而是按层号搜:哪个 bank 里装着本层,哪个就是本层要读的那一个(hit_bank)。这样三个 bank 怎么环转都不会读错。配套三处:

· 连续序列号 f_seq/lc_seq:层号每趟回绕(0..N-1),但「谁先谁后」不能回绕;用不回的序列号,才不会在趟边界把刚投过的层再投一遍。
· 消费即腾位:ST_WLD 搜到本层、换 bank_r 后立刻 lav[hit_bank]=0,泵下一拍就能把新层投进来。
· 模式切换认领:从两 bank 切进三 bank 时,池子是空的,但上一趟末尾已经把本趟首层投好了;开机那一拍把它认领进池子,不重复投。

新寄存器 WC_PF2(0x85C,1=三 bank 池);WC_PF2=0 就是第 41 环的两 bank 语义,一字未删。新探针 NET_PF2_CNT(连续泵投递次数)、NET_PF2_OVL(与计算重叠的投递字数)、NET_BANK_MSK(投递落过的 bank 集合)、NET_WL_STL(每层等链总拍数),新观察点 SCREEN17(0x870)。前 41 圈地址映射一字不改。

③ 怎么验证(硬证据)
证据结果说明
三 bank 池 vs 两 bank 乒乓输出逐位一致 0xFA03FC07只改了「链什么时候投」,算出来的东西一个字没变
NET_BANK_MSK011 → 111两 bank 只落 0/1;三 bank 三个都用上
4 趟×2 层,连续泵投递次数10 次每层都被提前投过,链不再「投一层歇一层」
快内存(链不是瓶颈)845 拍 vs 844 拍,持平诚实口径:链够快时,池子加深没有收益
慢内存(40 拍/字)6332 拍 < 6336 拍链一停就得等——池子把这段等待盖住了
阻塞等链(R39/40)/ 公式取(R28~30)逐位一致老路径一条没删,切回来输出不变
改一条层 0 描述符三 bank 池输出跟着变,还原后逐位回到原值投递听的是内存里描述符,不是写死的公式
SCREEN17 = 0x07060007
三 bank 全用上(7)| 连续泵投递 6 次 | P 趟首层等链 0 拍 | P 趟 lane0 = 7

单元仿真 tb_chain_pf2:两 bank 与三 bank 输出逐位一致;三 bank 掩码 111;慢内存 3 趟 6332 < 6336;单层、交替切、改描述符、老路径全部对表。

协同仿真 tb_min_soc42:先跑一趟 P0(池子从空到满的冷启动,允许等链,诚实口径),再跑 P 趟(稳态)——首层等链 0 拍、掩码 111、投递 6 次;SCREEN = 0xF905FD04、SCREEN2 = 0x02040404、SCREEN16 = 0x34000207 与第 25~41 环逐位一致;采样序列仍是 [0,2,0,0,0,3]。全量回归 71 项全绿。

④ 调试中钉住的钉子

其一(1 bit 的旧宽度)。rd_bank 是从两 bank 时代留下来的、没有位宽声明的 wire——那时 bank_r 只有 1 bit,看不出毛病;扩到三 bank 后 bank 2 被截成 0,计算读到另一个 bank 的旧字(gamma 用错层,输出全错)。加宽状态,先查所有「跟着状态一起宽」的线。

其二(start 那一拍的层号还是旧的)。非阻塞赋值让 lcnt<=0 在同一拍还没生效,池子搜索若复用 any_hit,搜的是「上一趟跑完的层号」。必须显式拿 0 去搜(any_hit_z)。

其三(跨模式的序列号是脏的)。两 bank 模式从不推进 f_seq;切进三 bank 时 f_seq=0 而 lc_seq=29,泵算出的「下一层」完全错位。修法:进池子时把 f_seq 同步到 lc_seq(或认领后 +1)。

其四(只读探针忘了加读口)。WC_PF2 写进去了、模式也生效了,但读回恒 0——min_bus 的读译码漏了 6'd23。自检因此假失败。写口和读口是两张表,加寄存器要两边都加。

其五(汇编层的两个小坑)。s12/s13 不是 RISC-V 的寄存器名(只有 s0~s11);0x868(a7) 的偏移超过 12 bit 有符号范围(a7 已经是 0x800 基址,应写 0x068(a7))。

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

第 40 环把「一趟之内」的取权重等待收进计算背后,第 41 环补上「趟与趟之间」,这一环补的是链本身不能停:权重池深到 2,链在算本层时可以把下两层都铺在路上。三层合起来,NPU 的阵列才真正「一直有活干」。

对一台手机来说,这就是内存墙前的那一步:模型权重不在片上,必须从内存一车车拉;拉得慢不是问题,拉到一半停下来等才是问题。池子把「等」变成「提前」,这是让一个真实尺寸的模型在手机功耗里跑得动的底层条件。