- isa:CAL_* 操作码连续占 24..31(CALL/RET 后移为 32/33,kOpCount=34);disasm 支持 5 个新操作码 - 词法/语法/链接:新 8 个关键字与冻结布局(TP=in/pt/q/et、CTD=cd/ld/pv/q/cv、 CTUD=cu/cd/r/lu/pv/qu/qd/cv、R_TRIG/F_TRIG=clk/q) - VM:do_cal 全 8 个语义(TP 脉冲、CTD 递减、CTUD 双向+装载、R_TRIG 上升沿、F_TRIG 下降沿); edge_prev_ 每槽 2 字节存边沿上次输入;exec_one 分发补 5 个新操作码 - 验证:TP 30ms 脉冲 2 周期、CTD 装载+递减到 q=1、CTUD cu/cd 双向、R/F_TRIG 交替; ctest 11/11 全绿 - 文档:指令与映像/词法/初步计划/符号表与链接/指令执行/扫描周期/使用说明 同步
7.3 KiB
7.3 KiB
指令执行流程(12.9)
vm 模块的指令执行语义:取指、译码、执行、故障。规范以 指令与映像.md 为准,本文描述执行器怎么跑一条指令。执行模型见 初步计划.md 第 2 节。
执行模型
每个扫描周期(run_cycle):
1. pc = 0,从入口函数(MAIN)开始逐条执行
2. 执行到 MAIN 的 RET → 周期结束
3. (I 采样 / Q 写回由 executor 按 sidecar 做,VM 只管数据区)
4. 定时器在 CAL_* 指令执行时用映像 dt_ms 推进
- pc:当前指令在函数字节码段内的下标(指令条数单位,从 0 起)
- 取指:函数表
func_row(fn_id)→code_offset(字节)→code_bytes[code_offset/4 + pc]读 u32(小端) - 译码:拆
op / rd / a / b四字段,a|b按操作码解释为寄存器对、槽号(u16)、或跳转偏移(int16)
帧与调用栈
struct Frame {
uint32_t fn_id; // 当前函数
std::vector<int64_t> regs; // 寄存器文件,大小 = 函数头 nregs
uint32_t ret_pc; // CALL 后应恢复的 pc(仅非 MAIN 帧有意义)
uint32_t ret_fn_id; // 返回目标函数(调用方)
};
- 调用栈
std::vector<Frame>,深度上限 64;超限 →StackOverflow故障 - MAIN 帧跨周期保留(PROGRAM 变量状态持久);周期结束只清调用栈(保留栈底 MAIN 帧)
- 帧寄存器初值 0
CALL / RET(调用约定,冻结于 12.8)
CALL fn_id:
1. 新帧:regs 大小 = callee 的 nregs,全部 0
2. 复制当前帧 r0..r7 → 新帧 r0..r7(实参已在调用点 MOVE 进 r1..r7)
3. 新帧 ret_pc = 当前 pc + 1(CALL 的下一条)、ret_fn_id = 当前函数
4. 压栈,pc = 0,fn_id = callee
RET:
1. 若当前是 MAIN(栈深 0)→ 周期结束
2. 否则:复制当前帧 r0..r7 → 调用方帧 r0..r7(r0 = 结果,r1..r7 原样返回)
3. 弹栈,pc = ret_pc,fn_id = ret_fn_id
值表示(寄存器 / 槽统一约定)
| 类型 | 表示 | 说明 |
|---|---|---|
| BOOL | 0 / 1 |
逻辑指令产出、字面量 TRUE=1 |
| INT | int16_t 符号扩展 |
sat_* 返回 int16,赋给 int64 寄存器自动符号扩展 |
| TIME | 全 64 位 | 毫秒 |
推论:全宽 int64 比较等价于各类型自身比较(INT 符号扩展后 -5 < 3 正确)。类型语义由编译期类型检查保证,VM 不查类型。
数据区访问(8 字节定宽槽,方案 a)
槽号 slot(指令操作数)→ 数据偏移 = slot × 8
LOAD_* rd, slot → rd = 读 data[slot*8 .. slot*8+8)(8 字节原样)
STORE_* slot, rs → 写 data[slot*8 .. slot*8+8) = rs(8 字节原样)
LOAD_I / LOAD_M / LOAD_GLOBAL / STORE_Q / STORE_M / STORE_GLOBAL 执行逻辑完全相同(v1 单一数据区,操作码只作语义标签)。
指令执行表
| 指令 | 流程 |
|---|---|
MOVE rd, rs |
reg[rd] = reg[rs] |
LOADK rd, cid |
查常量表 const_entry(cid):BOOL → 0/1;INT → (int16_t)value 符号扩展;TIME → 全宽 |
NOT rd, rs |
reg[rd] = (reg[rs] == 0) ? 1 : 0 |
AND/OR rd, ra, rb |
reg[rd] = (reg[ra] != 0 && reg[rb] != 0) ? 1 : 0(或 OR) |
ADD/SUB/MUL/DIV rd, ra, rb |
sat_add((int16)ra, (int16)rb) 等,结果符号扩展入 rd |
CMP_xx rd, ra, rb |
全宽 int64 比较 → 1 / 0 |
JMP off |
pc += off(见跳转语义) |
JT rd, off / JF rd, off |
reg[rd] != 0(JT)/ == 0(JF)时 pc += off,否则正常 +1 |
LOAD_* rd, slot |
见数据区访问 |
STORE_* slot, rs |
见数据区访问 |
CAL_TON/TOF/TP/CTU/CTD/CTUD/R_TRIG/F_TRIG slot |
见定时器与边沿推进 |
CALL fn_id / RET |
见帧与调用栈 |
跳转语义
指令执行后 pc 先 +1(指向下一条),跳转指令再 pc += off
⇒ 目标下标 = 当前 + 1 + off(与编译端 patch_jump 一致)
周期指令计数
- 每条指令执行后
cycle_count++;超过映像头cycle_limit→CycleLimit故障(周期中止) JMP/JT/JF/CALL只算 1 条(cycle_limit 以"执行的指令数"计)
定时器与边沿推进(CAL_TON / CAL_TOF / CAL_TP / CAL_CTU / CAL_CTD / CAL_CTUD / CAL_R_TRIG / CAL_F_TRIG)
CAL_* slot 的 slot 是实例基槽号;字段偏移 = 字段序号 × 8(布局冻结:TON/TOF/TP = in/pt/q/et;CTU = cu/r/pv/q/cv;CTD = cd/ld/pv/q/cv;CTUD = cu/cd/r/lu/pv/qu/qd/cv;R_TRIG/F_TRIG = clk/q)。每次执行 = 推进一次,Δt = 映像头 dt_ms(不读系统时钟)。边沿检测的"上次输入"存 VM 侧 edge_prev_(每实例基槽 2 字节),跨周期保留:
CAL_TON:
in 为真:et += dt(et ≥ pt 时停在 pt);q = (et ≥ pt) ? 1 : 0
in 为假:et = 0;q = 0
CAL_TOF:
in 为真:q = 1;et = 0
in 掉电:et += dt;et ≥ pt 时 q = 0
CAL_TP(脉冲):
in 上升沿启动 PT 时长脉冲(期间 in 变化不影响)
计时中:et += dt;et ≥ pt → q = 0、et 归零(脉冲结束);否则 q = 1
非计时:et = 0;q = 0
CAL_CTU:cu 上升沿 → cv += 1;r → cv = 0;q = (cv ≥ pv) ? 1 : 0
CAL_CTD:cd 上升沿 → cv -= 1;ld → cv = pv;q = (cv ≤ 0) ? 1 : 0
CAL_CTUD:cu 上升沿 → cv += 1;cd 上升沿 → cv -= 1;r → cv = 0;lu → cv = pv;
qu = (cv ≥ pv) ? 1 : 0;qd = (cv ≤ 0) ? 1 : 0
CAL_R_TRIG:q = clk 且上次 clk 假(上升沿)
CAL_F_TRIG:q = !clk 且上次 clk 真(下降沿)
故障
| 故障 | 触发 | 周期行为 |
|---|---|---|
None |
— | 正常完成 |
CycleLimit |
周期指令数 > cycle_limit(用例 6) |
中止,返回故障 |
StackOverflow |
调用栈深度 > 64 | 中止 |
BadOp |
op ≥ 29 | 中止 |
BadSlot |
slot × 8 + 8 > 数据区长度 | 中止 |
BadConst |
const_id ≥ n_consts | 中止 |
示例:line1 MAIN 逐指令推演
映像:dt_ms=10,cycle_limit=100000,数据区 56 字节(7 槽 × 8B,方案 a)。
初始:MAIN 帧 regs[13] 全 0;槽 0..6 全 0(初值 0)
假定 executor 已采样:槽 0 = EmergencyStop = 0、槽 1 = I0_0 = 1、槽 2 = I0_1 = 0
pc 指令 执行后
── ───────────────────── ──────────────────────────────
0 LOAD_I r8, 1 r8 = data[8..16) 低字节 = 1(I0_0)
1 STORE_GLOBAL r8, 4 槽 4(starter.start)= 1
2 LOAD_GLOBAL r9, 2 r9 = 0(I0_1)
3 STORE_GLOBAL r9, 5 槽 5(starter.stop)= 0
4 LOAD_GLOBAL r8, 8 内联体:r8 = 槽 4 = 1(start)
5 JF r8, +3 r8≠0 不跳;pc = 6
6 LOAD_GLOBAL r10, 9 r10 = 槽 5 = 0(stop)
7 NOT r9, r10 r9 = 1
8 MOVE r8, r9 r8 = 1
9 JF r8, +3 r8≠0 不跳;pc = 10
10 LOAD_I r12, 0 r12 = 槽 0 = 0(EmergencyStop)
11 NOT r11, r12 r11 = 1
12 MOVE r8, r11 r8 = 1
13 STORE_GLOBAL r8, 10 槽 6(starter.q)= 1
14 LOAD_GLOBAL r8, 10 r8 = 槽 6 = 1
15 STORE_Q r8, 3 槽 3(Q0_0)= 1
16 RET 周期结束;executor 读槽 3 → 硬件
结果:Q0_0 = 1(start=1、stop=0、急停=0 → 电机启动)——与 EXPECTED.md 真值表一致。