第 49 环 · 多会话槽位(按会话号分槽归档与恢复)
第 48 环让上下文能落内存、下次接着聊,可它只认一个会话区:归档和恢复都用同一个基址(0x000)。手机上不会只有一场对话——家人的、工作的、查资料的,各自都该记得自己的上下文。第二场一归档,第一场就被盖掉了。
本环给会话记忆区加一张槽位目录:会话号 → 槽位(基址)的映射由硬件维护,归档/恢复前核先问目录「这个会话住哪」:
新增 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 |
| A | open 会话 0x51 → 对话 → 归档 → commit | 槽 0 / 基址 0x000 / 归档 ok=1 / 12 字 |
| A' | 用 A 的上下文跑整网 | Y_A |
| B | open 会话 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 | 只作废槽 0 | find A miss / find B 仍命中槽 1 / used=1 |
单元仿真 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 化、跨设备同步、断电持久化。
现在「问目录 → 读基址 → 写 ARC_BASE → 发归档/恢复」仍是核一步一步搬。下一环把整件事收进一条 npu.sess:rs1=会话号、rs2=开/存/取/放,硬件自己查槽、自己把基址交给归档引擎。让「打开哪一场」也成为一个指令。