- 步骤 0:数据段 8 字节定宽槽(方案 a)——文档/codegen/测试三处落地 - 步骤 1~7:Machine.h API、译码 switch、CALL/RET 帧栈与故障、 定时器 Δt 推进、可观察性基础、vm_test(用例 06/17/18/20 + 确定性)、验证提交 - 指令执行语义指向指令执行.md
5.8 KiB
5.8 KiB
扫描周期
vm 模块:寄存器虚拟机。CMake 目标:vm(STATIC),只依赖 指令与映像.md。
运行时是 一台 VM、一个入口、一份映像。不链接 compiler。扫描周期与确定性见 初步计划.md 第 2、8 节。
扫描周期
1. 按 sidecar 的 io 绑定,采样 → I / 输入全局
2. 从 PROGRAM MAIN 的 pc=0 执行到 RET
3. 写回 Q / 输出全局
4. 用本周期 Δt 推进 TON / TOF / CTU(不用 wall clock)
边界
- 只认
isa映像;类型与溢出按指令与映像.md(INT饱和) - 局部 /
VAR= 固定寄存器;nregs在函数头 CALL目标是立即数fn_id,有界调用栈- 无 GC、无堆、无线程;每周期受
cycle_limit限制 - I/O 从哪来、Δt 谁给、回放怎么喂,属于
BytecodeExecutor(读.stb+ sidecar)
可观察性接口(v1 预留)
TUI 是执行器(BytecodeExecutor)的界面,用途是方便观察 VM 运行状态。v1 不做 TUI,只预留接口,避免后续改 VM 的观察面。原则:
- 数据与展示分离:
vm只提供只读观察 API(数据);TUI 渲染与文本输出属于executor(展示)。vm 不画任何界面。 - 只读轮询:TUI 通过快照轮询,绝不写 VM 状态;"继续 / 单步"是 executor 侧命令,不改变确定性。
- 快照 POD 化:快照保持简单数据(将来跨进程 TUI 可序列化)。
建议的 API 形态(12.9 实现时冻结签名,本节只定方向):
struct Snapshot {
uint32_t pc; // 当前指令下标(相对函数字节码段)
uint32_t fn_id; // 当前函数
uint32_t cycle_count; // 本周期已执行指令数
uint32_t call_depth; // 调用栈深度
// 寄存器 / I / Q / M / 全局 / FB 字段 的只读视图
};
bool step(); // 精确执行一条指令,停在指令边界
const Snapshot& snapshot() const;
- 寄存器 ≤256、映像很小,快照拷贝成本可忽略。
- 单步、回放、dump 都建立在同一套观察 API 上。
12.9 执行计划
指令执行语义(帧/调用栈、指令表、定时器、故障、line1 推演)见 指令执行.md。
步骤 0:数据段改 8 字节定宽槽(方案 a,前置)
VM 需要无歧义地访问数据区,当前变宽布局(BOOL 1B / INT 2B / TIME 8B 对齐)无法从槽号推断宽度。定案:每槽 8 字节定宽,指令 slot 语义 = 槽号(数据偏移 = slot × 8)。
- 文档修订:
Doc/isa/指令与映像.md(数据段规则 + slot 语义)、Doc/compiler/寄存器码.md(存储布局,global_offset映射取消)、Doc/isa/stb文件格式.md(line1 数据段 16B → 56B) Codegen.cpp简化:width_of/align_up删除、layout_data/layout_fb_instances按槽号布局、指令 slot = 符号表槽号codegen_test断言更新(slot 从字节偏移变槽号、数据区字节数变化)
步骤 1:Machine.h(新建 vm/include/vm/Machine.h)
namespace vm {
enum class Fault { None, CycleLimit, StackOverflow, BadOp, BadSlot, BadConst };
class Machine {
public:
static bool create(const std::vector<uint8_t>& image, Machine* out, std::string* err);
Fault run_cycle(); // MAIN pc=0 → RET(MAIN 帧跨周期保留)
// 可观察性(v1 预留,见上节):
bool step(); // 执行一条指令,停在指令边界
uint32_t pc() / fn_id() / cycle_count() / call_depth();
uint8_t* data(); // 数据区(I 采样 / Q 读回由外部做)
int64_t reg(uint8_t i); // 当前帧寄存器
};
}
Fault枚举 + 帧结构{ fn_id, regs[nregs], ret_pc, ret_fn_id };调用栈深度上限 64- 只依赖
isa(ImageView+ 类型/饱和函数)
步骤 2:Machine.cpp 译码 switch
- 取指:函数表 →
code_offset(字节)→ 读 u32 → 拆op/rd/a/b - 标量:
MOVE/LOADK(常量表 tag 定值表示)/NOT/AND/OR(布尔)/ADD/SUB/MUL/DIV(sat_*)/CMP_xx(全宽比较) - 跳转:
JMP/JT/JF(pc += off,相对下一条语义) - 数据区:
LOAD_I/LOAD_M/LOAD_GLOBAL、STORE_Q/STORE_M/STORE_GLOBAL(同一实现,slot × 8)
步骤 3:CALL / RET 帧栈 + 故障
CALL fn_id:压新帧(复制 r0..r7)→ pc=0;RET:复制 r0..r7 回调用方 → 弹栈(MAIN 的 RET = 周期结束)cycle_limit超限(用例 6)、栈深 > 64、BadOp/BadSlot/BadConst→ 周期中止返回故障
步骤 4:定时器(CAL_TON / CAL_TOF / CAL_CTU)
- 执行时用映像
dt_ms推进(每周期一次调用 = 推进一次),不读系统时钟 - TON:in 真 → et += dt(到 pt 停)、q = et ≥ pt;in 假 → et = 0
- TOF:in 真 → q=1、et=0;掉电 → et += dt、et ≥ pt → q=0
- CTU:cu 上升沿(上次 cu 存 VM 侧 vector,按实例基槽索引)→ cv+1;r → 复位;q = cv ≥ pv
步骤 5:可观察性基础
step()/pc/fn_id/cycle_count/call_depth/reg落基础实现;snapshot()留 12.11 补全
步骤 6:vm_test(tests/src/vm_test.cpp + CMake,链 vm 库)
- 手工拼小映像(
isa编码函数):赋值、跳转、CALL/RET 帧复制、cycle_limit - 编译器产物全链路:用例 01/02/04/05/07/09/13/15/17/18/20 跑周期断言寄存器与数据区
- 用例 06:
cycle_limit打满 →CycleLimit故障 - TON:dt=10、pt=30 → 第 3 周期 q=1;CTU:上升沿计数
- 确定性:同一 I 序列跑两遍,数据区完全一致
步骤 7:验证 + 提交
cmake --build+ctest(新增vm_cycles后 10/10 全绿);20 用例扫描:14 正例执行通过