🎯 圆心 · 目标(每环都从这里出发)
造一台 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 化 · ㊻对话回路 · ㊼对话 ISA 化 · ㊽会话记忆归档 · ㊾多会话槽位 → ㊿…

本环:让「记得你」从一条记忆,变成一本分区清楚的通讯录

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

第 48 环让上下文能落内存、下次接着聊,可它只认一个会话区:归档和恢复都用同一个基址(0x000)。手机上不会只有一场对话——家人的、工作的、查资料的,各自都该记得自己的上下文。第二场一归档,第一场就被盖掉了。

本环给会话记忆区加一张槽位目录:会话号 → 槽位(基址)的映射由硬件维护,归档/恢复前核先问目录「这个会话住哪」:

  • open:按会话号查;没有就分配一个空槽,满了按轮转指针换掉一槽;
  • find:只查不分配(这个会话以前来过吗);
  • commit:一场归档完成后,把摘要/字数/KV 条数写进目录项;
  • free:按会话号作废一个槽(保留别人的);
  • reset:目录重建(写魔术字、六个槽清空)。
② 实现(做了什么)

新增 rtl/sess_slots.sv(会话槽位目录引擎),并把它接进 min_bus 的会话记忆区:

会话记忆区 512 字:
  0x000~0x17F  六个数据槽,每槽 64 字
               槽0=0x000 槽1=0x040 槽2=0x080
               槽3=0x0C0 槽4=0x100 槽5=0x140
  0x180~0x18F  目录表头 {魔术字 "SLOT", used, stamp, 轮转指针}
  0x190~0x1A7  六个目录项,每项 4 字
               {valid|sid, base, digest, {words,kcnt,vcnt}}

目录操作全在硬件里完成:搜索六个目录项 → 命中/分配 → 写目录项 → 回读摘要与字数。空内存不是错误:open 看到魔术字不对,先把目录建起来再照常分配——首次开机不该崩。

min_bus 新页 0xC00(bit11+bit10):
0xC00 SS_CMD   0xC04 SS_SID   0xC08 SS_GO   0xC0C SS_CDIG
0xC10 SS_CWRD  0xC14 SS_RST   0xC18 SCREEN25
读回:0xC20 BUSY  0xC24 DONE  0xC28 HIT   0xC2C SLOT
      0xC30 BASE  0xC34 USED  0xC38 MAGIC 0xC3C ERR
      0xC40 STAMP 0xC44 ESID  0xC48 EDIG  0xC4C EWORDS
      0xC50 EVALID
归档引擎的 base 从固定 0x000 改成可编程:0xA4C ARC_BASE

两个引擎共用同一块会话内存的读写口:核先问目录、再发归档;归档跑时目录闲着。min_bus 里做一层二选一,并给启动加互斥——目录忙时归档不启动,归档忙时目录不启动。

③ 怎么验证(硬证据)

协同仿真 tb_min_soc49 在既有全部观察点之后跑一段完整的「两会话」流程:

步骤做什么结果
reset目录重建魔术字 SLOT / used=0
Aopen 会话 0x51 → 对话 → 归档 → commit槽 0 / 基址 0x000 / 归档 ok=1 / 12 字
A'用 A 的上下文跑整网Y_A
Bopen 会话 0x52 → 另一场对话 → 归档 → commit槽 1 / 基址 0x040 / used=2
B'用 B 的上下文跑整网Y_B ≠ Y_A
find A只查不分配槽 0 / 基址 0 / 摘要 == A commit 的摘要
restore A从槽 0 恢复 → 跑整网Y_A2 == Y_A(一直没关)
find/restore B从槽 1 恢复 → 跑整网Y_B2 == Y_B,且 ≠ Y_A2
find 0x77未知会话hit=0 / err=0(查不到不是错误)
free A只作废槽 0find A miss / find B 仍命中槽 1 / used=1
SCREEN25 = 0x01FF0240
槽A=0 | 槽B=1 | 八位证据全 1 | used=2 | baseB 低 8 位 = 0x40

单元仿真 tb_sess_slots:空内存自动建目录;两个会话分槽;find 不改目录;commit 后 find 读回同一摘要/字数;free 只作废指定会话;空槽优先复用;六槽填满后按轮转换槽、被换出的会话 miss、新会话命中;满目录 open 已存在会话不挪位;reset 清空全部会话。

主通路未动:SCREEN=0xF905FD04、SCREEN2=0x02040404、SCREEN23=0x08020500、SCREEN24=0x10000F04 与第 25~48 环逐位一致。

④ 调试中钉住的钉子

其一(表头最后一字读到的是上一轮的旧值)。目录指针住在表头第 4 字。读表头用了 h=0..3:h=3 那一拍 hdr[3] 正在被写入(非阻塞),同一拍读 hdr[3] 拿到的是复位值 0。于是「满目录轮转」永远换成槽 0,而不是指针指的槽 1。改成在 h==3 那拍直接取 mem_rdata。教训:流水线里「刚写进寄存器的值」这一拍还读不到,要用入口那一份。

其二(free 只清了目录项,没清表头 used)。作废一个会话后 used 仍停在旧值,下一条 open 读到 used 偏大,最终 used=7(六个槽却有七个)。free 现在是三拍写:清目录项、清旧会话表头、写回表头 used−1。

其三(换槽被当成新槽)。满目录 open 新会话时复用一槽,used 不该再加一。第一版无条件 +1,used=7 且后面的 find 全乱。分配时先判「是否替换」:空槽 +1,换槽不变。

其四(commit 认错了「上一次命中」)。commit 原本看 e_valid,可每次 start 都会清它——commit 永远报「没有槽」。改成独立的 last_slot/last_valid,由 open/find 命中时更新,free 到同一槽时才清。

其五(目录启动撞上第 48 环的自动归档)。R48 最后一场对话结束时自动归档还在跑,R49 的 SS_GO 单拍被内存互斥挡掉、丢了,busy 一直 0、魔术字读 0。开头先等 ARC_BUSY 落,再动目录。教训:脉冲启动的引擎,先确认对方真的闲。

其六(字数别写死 16)。R48 是两回合 7 个 token、环深 4 → 16 字;R49 单回合 3 步 → 每层 3 条 → 12 字。第一版断言 16 直接失败。归档多少字就提交多少字,目录回读也按实际值比。

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

到这一环,Salt OS 的「同一个对话窗口」有了分区记忆:不同会话各住一槽,关掉再打开各自回到自己的上下文;恢复 A 得 Y_A、恢复 B 得 Y_B,两者不同——不是同一个槽的别名。释放 A 之后 B 仍在,说明槽位是独立的,不是一个全局开关。

对一台手机的意义:一个人一天里本来就有几场对话。只有「每场都记得自己」,「AI 总调度」才不会把家里的事串到工作里;也只有这样,第三方服务接进来时才能各自带着自己的上下文,而不是共用一锅粥。再往上,是槽位 ISA 化、跨设备同步、断电持久化。