新增指令执行流程文档(12.9 前置)。
- Doc/vm/指令执行.md:执行模型、帧/调用栈(CALL/RET 复制 r0..r7)、值表示约定、 8 字节定宽槽访问、29 条指令执行表、跳转语义、cycle_limit 计数、 CAL_TON/TOF/CTU Δt 推进、五类故障 - line1 MAIN 逐指令推演(I0_0=1、stop=0、急停=0 → Q0_0=1,对照真值表) - 索引登记
This commit is contained in:
+160
@@ -0,0 +1,160 @@
|
||||
# 指令执行流程(12.9)
|
||||
|
||||
`vm` 模块的指令执行语义:取指、译码、执行、故障。规范以 [`指令与映像.md`](../isa/指令与映像.md) 为准,本文描述**执行器怎么跑一条指令**。执行模型见 [`初步计划.md`](../初步计划.md) 第 2 节。
|
||||
|
||||
## 执行模型
|
||||
|
||||
```text
|
||||
每个扫描周期(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)
|
||||
|
||||
## 帧与调用栈
|
||||
|
||||
```cpp
|
||||
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)
|
||||
|
||||
```text
|
||||
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)
|
||||
|
||||
```text
|
||||
槽号 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/CTU slot` | 见定时器推进 |
|
||||
| `CALL fn_id` / `RET` | 见帧与调用栈 |
|
||||
|
||||
### 跳转语义
|
||||
|
||||
```text
|
||||
指令执行后 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_CTU)
|
||||
|
||||
`CAL_* slot` 的 `slot` 是实例**基槽号**;字段偏移 = 实例布局字段序号 × 8(内建布局冻结:TON/TOF = in/pt/q/et,CTU = cu/r/pv/q/cv)。每次执行 = 推进一次,Δt = 映像头 `dt_ms`(不读系统时钟):
|
||||
|
||||
```text
|
||||
CAL_TON:
|
||||
in = 槽[in] 非零;pt = 槽[pt];et = 槽[et];q = 槽[q]
|
||||
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
|
||||
(in 再次为真 → 重新 q = 1, et = 0)
|
||||
|
||||
CAL_CTU:
|
||||
上升沿检测:本周期 cu 为真且上次 cu 为假 → cv += 1
|
||||
(上次 cu 存 VM 侧 vector,按实例基槽索引,跨周期保留)
|
||||
r 为真 → cv = 0
|
||||
q = (cv ≥ pv) ? 1 : 0
|
||||
```
|
||||
|
||||
## 故障
|
||||
|
||||
| 故障 | 触发 | 周期行为 |
|
||||
|---|---|---|
|
||||
| `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)。
|
||||
|
||||
```text
|
||||
初始: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` 真值表一致。
|
||||
Reference in New Issue
Block a user