🎯 圆心 · 目标(每环都从这里出发)
造一台 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 化 → ㊲描述符链 → ㊳链槽直喂 → ㊴主路走链 → ㊵…

本环:链扫得完、槽喂得动,可主路还在走老路

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

前几环把「扫一整层权重」「把扫完的槽直喂子层」都收进了硬件。可这些本事只在收尾试验里跑:解码主路的每层权重,走的还是第 28~30 环那条老路——整网引擎自己按写死的公式「层号 × 16 + 层内序号」从权重 SRAM 流式取。

公式把权重的住处钉死了:想搬到别处不行、想让两层共用一份矩阵不行、想按别的顺序摆不行。真芯片上权重就是内存里一堆矩阵,谁住哪、投到哪个用途该由内存里的描述符说了算,不该由一条乘法公式说了算。这一环让主路走链。

② 实现(做了什么)

新增 rtl/wei_chain.sv 权重描述符链引擎:内存里排一串权重描述符,每条说清「这张矩阵是哪一层的、投到哪个用途槽、几个字、住在哪」:

描述符(32bit) = { 层标签[31:24], 用途槽[23:20], 字数[19:8], 基址[7:0] }

用途槽 = 整网引擎里那几个消费寄存器的组号:
  0=Wq(头0查询)  1=Wq2(头1查询)  2=K/V投影  4=FFN两层  6=LN仿射
槽 s 的第 j 个字 → 消费寄存器序号 = 起始序号(槽) + j
(与第 28 环「层 × 16 + 序号」的字布局一一对齐 → 链投的字与装载器取的字落在同一个寄存器)

整网引擎在等本层权重时把 req 拉高、同时给出层号;引擎按描述符一条条取字、边取边直投进层权重寄存器(wr_en/wr_idx/wr_data 片上直连),别的层的条目硬件自己跳过——「哪些条目属于本层」是硬件判的,核没参与筛选。本层投完 rdy 拉高。

ISA 上不加新指令:主路启动仍是第 27 环那三条 npu.net / npu.wait / npu.rd——本环换的是「权重从哪来」,不是「核怎么发指令」,解码循环一行未改。核只写三个寄存器:WC_MODE(主路走链)/ WC_TBLS(链条目起始字)/ WC_N(链长)。新页 0x800~0x838(bit11 页,前 38 圈未用过),前 38 圈地址映射一字不改。

③ 怎么验证(硬证据)
证据结果说明
解码循环整个改走链SCREEN / SCREEN2 / SCREEN3 与第 25~38 环逐位一致换了权重的来路、算出来的东西一个字没变
NET_CHN_WRD(引擎侧)28 字 / 趟(2 层 × 14 字)本层权重真是链直投进来的
NET_CHN_WAIT> 0等权重的那些拍里,等的是链,不是装载器
WC_WRD / WC_SKIP / WC_ENT(链侧)168 字 / 跳 60 条 / 走 120 条6 步 × 2 层 = 12 次请求;每次跳过另一层 5 条——核没筛过层
WC_TAG(末条目标签)0x01直接从内存里的描述符读来,核没写过这个字段
尾巴 A(链)vs B(公式)逐位一致同一份计算,只是「谁把权重送进来」不同
尾巴 C(改一条描述符:层0 LN 住处 12→14)≠ A描述符的基址真管着权重——不是摆设
SCREEN14 = 0x1C3C0107
一趟直投 28 字 | 跳过条目 60(0x3C) | 末条目标签 0x01 | A 趟 lane0 7

单元仿真 tb_wei_chain:一层权重按内存里的描述符投进消费寄存器、别的层的条目硬件自己跳过(skip=5)、槽号说了算(描述符顺序打乱,每个字照样落回自己的槽)、别名(层1 的 Wq 指向层0 的地址 → 两层共用一张矩阵)、空链不挂死 / clr 清探针 / 重放逐位一致——全通。

协同仿真 tb_min_soc39:整机跑完解码循环(层权重全程由链直投)→ 采样序列仍是 [0,2,0,0,0,3],第 6 步吐停止符 3 而停;SCREEN = 0xF905FD04 与第 25~38 环逐位一致。全量回归全绿。

④ 调试中钉住的钉子

其一:新页的位选要抢在老的页内再分页之前。新页 0x800 的 addr[10:8] 全是 0,会被前几圈那条 sel_r0 分支先接走——写进去石沉大海。bit11 页的解码必须放在最前面。

其二:链的 rdy 是电平,不是脉冲。核在 ST_WLD 看到它才开算;可等跑到 ST_LB 要用「本层 FFN 块到齐」时,req 早已落下、rdy 也跟着落下——不把它锁进一个标志(chn_ok_r),ST_LB 会永远等一个已经消失的握手(死锁)。

其三(参考模型里的同名覆盖)。本环的尾巴对照在参考模型里也重放了一遍,用的变量名也叫 yA/yB/yC——正好把第 28~30 环那套同名的模型值覆盖掉了。于是第 38/56 子阶段的期望值被悄悄换成了尾巴对照的值:硬件给 0xF703F90A,生成器却写 0xFB04FB07,仿真报了一个假失败。修法很土很有效:尾巴那套前缀 r39_。

其四(尾巴的寄存器其实没摆对)。npu.net 的两个操作数是 s5/s6(第 27 圈起的约定),不是注释里写的 s10——尾巴只写了 s10,于是悄悄沿用了第 37/38 段留下的 s5=0x4E / s6=1072(层数被截成 0)。跑的既不是对照输入也不是 2 层,A 趟输出自然对不上。教训:注释说的不等于指令真吃的,摆操作数要对着编码核一遍。

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

第 37 环让「扫一整层」成了一条指令,第 38 环让「扫完即用」不用回核里绕一圈;这一环让主路本身也走这条路——解码循环的每一层权重,都由内存里的描述符链决定住处与去处。核在权重这件事上从「取数的人」退成「给个链在哪、多长的人」。

离目标更近的一点在于:真模型的一层权重是几 MB 到几 GB、切块摆在不同区域,甚至多层共用同一份矩阵(权重共享)——这些都能被描述符表达,而公式永远表达不了。当主路也走链,整台机器就从「按公式找权重」变成了「按内存里的清单取权重」,这才装得下真正的大模型。