🎯 圆心 · 目标(每环都从这里出发)
造一台 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 · ㊸权重池 ISA 化 · ㊹一步生成 ISA 化 · ㊺整段生成 ISA 化 → ㊻…

本环:一整句话,一条指令说完

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

第 44 环把「一步解码」收进了一条 npu.step:词嵌入、整网 N 层、LM 头、采样器、生成控制器登记全在片内串完。可一步只是一个字。真回答是一串字,于是核还得在软件里走循环:

loop:
  npu.step → npu.wait → npu.rd token
  若未停:把 token 塞回输入 → 再走一步

六步就是六轮指令、六次往返;步数一多,核又变回「串步子的传送带」。本环把整段生成也收进一个引擎:核给一个 prompt、一条 npu.generate,引擎在片内自己「跑一步 → 把 token 反馈成下一步输入 → 看生成控制器停没停 → 未停再来一步」,一直跑到停止符或长度上限,才回核一次。

② 实现(做了什么)

新增 rtl/gen_run.sv:四态循环 IDLE → GO → WAIT → CHK。

start  :锁存 prompt / 层数,busy 拉高,gen_start 单拍(清生成控制器、开新一段)
GO     :step_tok = 上一步输出(第一步 = prompt),step_start 单拍
WAIT   :等 step_done;收到后停一拍再判(生成控制器在登记那拍之后才更新 done)
CHK    :步数 +1、序列左移 2bit 塞入 token;gen_done 或到本地 MAX_STEPS → done_pulse,否则回 GO

关键点是反馈线在片内:上一步的 step_tok_out 直接进下一步的 step_tok,核不碰。停止判断也分两层:gen_ctrl 按「停止符 / 最大步数」给 gen_done,gen_run 另有一个 MAX_STEPS 兜底——即使生成控制器配错,也不会挂死。

ISA:核升 v13,custom-1 下加 funct3=010:

npu.generate rs1, rs2     rs1 = prompt token,rs2 = 整网层数
npu.wait                  整段没跑完,整条流水线冻在这条指令上
npu.rd rd, sel=11         token 序列(每步 2bit,最近 16 步)
npu.rd rd, sel=12         本段跑了几步
npu.rd rd, sel=13         状态 {n_steps, stop_hit, gen_done, done, busy}
npu.rd rd, sel=14         最后一个 token

min_bus 新页 0x8C0~0x8F0:GENRUN_PROMPT / GENRUN_LAYERS / GENRUN_GO / GENRUN_BUSY / GENRUN_N / GENRUN_SEQ / GENRUN_STATUS / GENRUN_CORE(核写入口次数)/ GENRUN_RST / GENRUN_RUNS / GENRUN_ST / GENRUN_HDIFF(整段里两头是否看过不同角度)/ SCREEN20。前 44 圈地址映射一字不改,npu.step 老路径一条不删。

③ 怎么验证(硬证据)

把主路解码段从「六组 npu.step + 软件喂回」整段换成一条 npu.generate,其余一字不动——若整段生成与老路径有任何一位不同,后面那一长串 SCREEN 立刻对不上:

证据结果说明
整段 token 序列[0,2,0,0,0,3] → 0x0203由硬件自己反馈,核没喂过一步
步数GENRUN_N = 6一条指令里真跑了六步
停止原因stop_hit = 1最大步数特意调到 8,停只能来自停止符 3
核搬运入口寄存器次数GENRUN_CORE = 0这段里核一次没写 EMB_TOK/LM_X/LM_START/SMP_START/GEN_STEP_W/NET_X/NET_LAYERS/NET_GO
状态字0x0600000E{n=6, stop_hit=1, gen_done=1, done=1, busy=0}
最终残差流0xF905FD04与第 25~44 圈逐位一致(主通路没被动过)
SCREEN2~19全部逐位一致十七个观察点一个没漂
SCREEN19(一步生成)0x06000203六步 step_gen 计数照旧、核搬运仍为 0
SCREEN20 = 0x020306E0
序列 0x0203 | 步数 6 | stop_hit 1 | gen_done 1 | 核搬运 0

单元仿真 tb_gen_run:用「按拍数忙闲」的一步引擎替身 + 真的 gen_ctrl,钉死五件事——① 一条 start 只开一段、段内每步只发一次 step_start(忙时再发 start 不重入);② 上一步的 token 被硬件反馈成下一步输入(输入序列逐位对表);③ 命中停止符就停(n=6、seq=0x0203、stop_hit=1);④ 到长度上限也停(n=4、stop_hit=0、不会挂死);⑤ clear 归零、第二段从新 prompt 重新开始。

协同仿真 tb_min_soc45:一条 npu.generate 跑完整段,SCREEN=0xF905FD04、SCREEN2~19 与第 25~44 圈逐位一致、SCREEN20=0x020306E0。全量回归 77 项全绿。

④ 调试中钉住的钉子

其一(少接一根线,整网层数变 0)。gen_run 把层数锁在自己寄存器里,却忘了给 step_gen 的 layers_in 加一条来路——step_gen 仍只认「核指令 / MMIO」,于是每次一步的 rs2 都是 0,整网一层都没跑。输出看起来「有点样子」(走了词嵌入和 LM 头),特别容易误判成模型问题。教训:新增一条启动来路时,所有随行操作数都要一起接,不能只接 start 和 token;仿真里直接把 net_start 那拍的 x/layers 打出来,一眼就能看见 layers=0。

其二(寄存器编号写错,检查读了个空寄存器)。生成脚本里把 s10 的编号写成 18(其实是 s2),npu.rd 把序列写进了 s2,后面的检查却读 s10(x26)——s10 从来没被写过,永远是 0,于是「序列不对」自曝。final 寄存器 dump 里 s2=0x203、s10=0 才露出真相。教训:汇编器不检查你的寄存器别名;s10 = x26,s11 = x27,别凭感觉写。

其三(ALU 结果进分支,要两拍)。状态检查是 andi t1, s3, 0xF 后直接 bne t1, t2。这个核的分支在 IF 拍解析,ALU 结果要走到 MEM 转发才稳;只隔一拍时读到的是旧值 0,明明状态是 0xE 却判失败。补一拍 nop 后通过。教训:凡是用 ALU 结果做分支条件,隔两拍;用 npu.rd/lw 的结果做分支也一样要给足写回时间。

其四(老自检的累计口径,换了执行方式就失效)。第 26 环的「多头真在主路里」自检靠六步逐步累计 s9;整段生成后核不再逐步读 npu.rd sel=2,而最后一步两头的落点恰好可能相同,s9=0 就误报。解法不是跳过检查,而是把「整段里是否出现过两头不同」做成硬件粘滞位 GENRUN_HDIFF:每个 step_done 比较 net_slot 的两个 2bit 字段,出现一次就置位,整段结束由核读一次。检查的语义保住了,只是把「累计」从软件挪回硬件。

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

到这一环,生成一句话对软件只剩一句话:「给这个 prompt,生成到停」。词嵌入、整网、LM 头、采样、token 反馈、停止判断——全在片内自己走。核从「走循环的人」退成「发一段、等一段、读一串」的调用者。

对一台手机的意义:一次回答的固定开销,从「每步三条指令 × 几十步」缩成一条指令;OS 不再需要保存解码循环的状态(上一步 token、步数、停没停),这些都在硬件里。更关键的是语义边界清楚了——「生成一段」是一个不可分割的硬件动作,正好接上 Salt OS 的对话窗口:用户说一句、AI 生成一段、显示在同一窗口里。