第 47 环 · 对话 ISA 化(一条 npu.chat 跑完整场对话)
第 46 环把「一场多回合对话」做进了硬件:一个 CONV_GO,芯片自己跑「回合 0 → 回合边界 → 回合 1」。
可开这场对话的钥匙还在软件手里:核要先写 5 个寄存器——CONV_PROMPTS / CONV_NTURNS / CONV_MODE / CONV_LAYERS / CONV_GO。写错一个,整场对话就静默变样;换一场就再抄一遍。这和第 27 环之前的 npu.net、第 43 环之前的权重池是同一个病:硬件会跑,但「怎么配」还是核的活。
本环把整场对话收进一条指令:
npu.chat rs1, rs2
rs1 = 每回合输入队列(4×2bit,回合0 在低位)
rs2 = {carry, ctx, 层数[1:0], 回合数[1:0]}
核发一条、等一条、读一串——一个配置寄存器都不写。
核升 v14:custom-1(0x2B)加 funct3=011 的 npu.chat,EX 拍把 rs1/rs2 直接送进对话引擎。前 46 圈的 custom-0 / custom-1 编码一位不动。
conv_loop.start 现在有两个来路——CONV_GO(第 46 环老路,原样保留)和 npu.chat 的 EX 脉冲;配置从「MMIO 寄存器」二选一换成「指令操作数」。npu.rd 选源从 4bit 放宽到 5bit,新增 6 个选源:15 整场 token 序列、16 状态(清上下文次数 / token 总数 / 回合数 / done / busy)、17 每回合末 token 与步数、18/19 回合 0~3 各自序列、20 末回合真实输入。CHAT_WCORE(0x94C)。指令路径应为 0,MMIO 老路为 5——和 POOL_WCORE 同一个探针思路。CONV_RST 同时清记录与 CHAT_WCORE,让「一场对话」的计数从零起算。rtl/riscv_core.sv 升 v14:npu.chat 解码 + 6 个读回选源(15~20)
rtl/min_bus.sv 对话引擎启动/配置二选一;CHAT_WCORE 计数;
新读口 0x94C / 0x950(SCREEN23)
rtl/conv_loop.sv 一字未改(第 46 环的引擎直接复用)
OS 主循环里跑两场同样的对话:
| 会话 | 配法 | 要证明什么 |
|---|---|---|
| D | 一条 npu.chat + 一条 npu.wait | 整场对话(2 回合)跑完;核写配置 0 次;npu.rd 读回整份记录 |
| E | 5 条 sw(老路)+ CONV_GO | 同样的输入/模式,结果与 D 逐位一致;核写配置 5 次 |
所有既有观察点先跑完,SCREEN~SCREEN22 一个没动;对话 ISA 试验插在全部收尾之后,只新增 SCREEN23。
| 证据 | 结果 | 说明 |
|---|---|---|
| 一条指令跑几回合 | turns = 2 | npu.chat 一条 + npu.wait 一条,两个回合全在片内 |
| 整场 token 数 | 8 | == 两回合步数之和(3 + 5,硬件自报,核不算) |
| 整场记录 | 0x00002E67 | npu.rd sel=15 读回;每回合序列 0x0267_000B |
| 末回合真实输入 | PIN = 1 | 队列第 2 句,硬件自报 |
| 会话边界 | ctxclr = 1 | ctx=1:只在回合 0 清一次,跨轮上下文保留 |
| 核写配置次数 | 0(指令)vs 5(MMIO) | CHAT_WCORE 硬件计数——「指令即配置」的硬证据 |
| 两条路径的结果 | 逐位一致 | transcript / tseq01 / PIN 全部相同(E == D) |
| 主通路 | SCREEN = 0xF905FD04 | 与第 25~46 圈逐位一致;SCREEN2~22 一个没漂 |
协同仿真 tb_min_soc47:D/E 两场全跑通,SCREEN=0xF905FD04、SCREEN21=0x005B000B、SCREEN22=0x12470E0F 与第 25~46 圈逐位一致;另跑 tb_conv_loop / tb_gen_run / tb_min_soc45 / tb_min_soc46 回归全绿。
其一(4bit 常量把 16~20 截回了 0~4)。npu.rd 的选源加宽到 5bit 后,npu_rd_data 里新写的比较仍是 4'd16 … 4'd20。Verilog 会把 4'd16 截成 0、4'd17 截成 1……于是 sel=16 读回的是 npu_y、17 读回的是层号,六个新选源全部「有值但不对」。改成 5'd16 … 后立刻正确。教训:加宽总线时,所有常量也要一起加宽;4'd16 不报错,只静默截断。
其二(会话复位没清计数)。第一版只让 CONV_RST 清对话引擎的记录,忘了清 CHAT_WCORE——A/B/C 三场跑完计数是 15,指令路径读回不是 0,整条证据链假失败。教训:「复位」要复位这一页的全部可观察量,不只是数据面。
其三(比较前先回同一颗种子)。两条路径要比「同一场对话」,采样器必须先回到同一颗种子(SMP_SEED=0xA5),否则答案是两次不同的抽样,比不出等价性。这是对照实验的基本纪律,不是硬件问题。
其四(第 46 环留下的「首段热身」仍未定位)。同一份配置、同一颗种子,第 47 环的 D 场与第 46 环的 A 场在第一回合逐位相同(0x000B)、第二回合不同(D 多走一步)。而 D 与紧接其后的 E(MMIO 老路)逐位一致。说明:① ISA 路径与 MMIO 路径的等价性成立;② 「换场次后首段仍受上一场历史影响」这颗钉子还在(第 46 环已记录),本环不拿它当验收前提,明确留给后续环。教训:跨场次的确定性要单独做一条「同配置跑两场」的回归,而不是靠一次对比。
到这一环,OS 看一场对话只剩一句话:「这几句,接着聊」——连「这场对话怎么配」都不用写了。回合数、要不要记忆、接不接话、层数,全在一条指令的两个操作数里;回答、步数、每回合说了什么,全从 npu.rd 一次读回。
对一台手机的意义:对话是 Salt OS 唯一的界面。界面背后的「一场对话」现在是一条指令的语义,而不是驱动里的一段配置代码——这意味着换一个模型、换一个服务、换一块主板,对话这件事的契约不变。再往上,才是「为别人搭台」:每个服务接的都是同一条 npu.chat,同一份会话记录。
一场对话已经能一条指令跑完,可它只活在片上的 K/V 环里:会话一结束、或环被写满,记忆就没了。下一环做会话记忆归档——一场对话结束时把 K/V 上下文落到内存,下次开同一场对话时再装回来,关机/换场次也能接着聊。这是「AI 记得你」从片上缓存走向持久化的第一层。