阶段5-2:文档收尾——vm 指令执行/扫描周期、compiler 寄存器码/符号表/STCompiler说明、executor 入口全部更新为 V2

This commit is contained in:
2026-08-26 10:40:20 +08:00
parent 98d2e86dca
commit 84de006665
6 changed files with 242 additions and 275 deletions
+33 -85
View File
@@ -1,24 +1,24 @@
# 扫描周期
vm 模块:寄存器虚拟机。CMake 目标:`vm``STATIC`),只依赖 [`指令与映像.md`](../isa/指令与映像.md)。
vm 模块:寄存器虚拟机(V2:变长指令 / 槽表两级 / 栈方案 C)。CMake 目标:`vm``STATIC`),只依赖 [`指令与映像.md`](../isa/指令与映像.md)。
运行时是 **一台 VM、一个入口、一份映像**。不链接 `compiler`。扫描周期与确定性见 [`初步计划.md`](../初步计划.md) 第 2、8 节。
## 扫描周期
```text
1. 按 sidecar 的 io 绑定,采样 → I / 输入全局
1. 按 sidecar 的 io 绑定,采样 → I / 输入全局executor 写值区槽)
2. 从 PROGRAM MAIN 的 pc=0 执行到 RET
3. 写回 Q / 输出全局
4. 用本周期 Δt 推进内置 FB(不用 wall clock
3. 写回 Q / 输出全局executor 读值区槽)
4. 用本周期 Δt(映像头 dt_ms推进内置 FB(不用 wall clockRTC 用累计周期数
```
## 边界
- 只认 `.stb` 映像(`vm` 自带读实现);读取时**型号标识匹配 + SHA-256 校验**,不符/篡改 → 拒绝
- 类型与溢出按 [`指令与映像.md`](../isa/指令与映像.md)`INT` 饱和);指令定义归 `isa`
- 局部 / `VAR` = 固定寄存器;`nregs` 在函数头
- `CALL` 目标是立即数 `fn_id`,有界调用栈
- 只认 `.stb` 映像(`vm` 自带读实现);读取时**型号标识匹配STATOR2+ SHA-256 校验**,不符/篡改 → 拒绝
- 类型与溢出按 [`指令与映像.md`](../isa/指令与映像.md)整数饱和 / 除法 A / 浮点 IEEE);指令定义归 `isa`
- 局部 / `VAR` = 固定寄存器;`nregs` 在函数头;栈区预分配(`max_stack`,编译期 DAG 最坏路径)
- `CALL` 目标是立即数 `fn_id`,有界调用栈(深度 64 双检查)
- 无 GC、无堆、无线程;每周期受 `cycle_limit` 限制
- I/O 从哪来、Δt 谁给、回放怎么喂,属于 `BytecodeExecutor`(读 `.stb` + sidecar
@@ -32,19 +32,21 @@ TUI 是执行器(`BytecodeExecutor`)的界面,用途是方便观察 VM 运
- **只读轮询**:TUI 通过快照轮询,绝不写 VM 状态;"继续 / 单步"是 executor 侧命令,不改变确定性。
- **快照 POD 化**:快照保持简单数据(将来跨进程 TUI 可序列化)。
建议的 API 形态(12.9 实现时冻结签名,本节只定方向):
已落地的观察面(`Machine` 公共接口):
```cpp
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;
bool step(); // 精确执行一条指令,停在指令边界
Fault fault() const; // 最近一次故障
bool ended() const; // 周期是否已结束
uint32_t pc() const; // 当前 pc(字节,相对本帧函数)
uint32_t fn_id() const; // 当前函数
uint32_t cycle_count() const; // 本周期已执行指令数
uint32_t call_depth() const; // 调用栈深度
uint8_t* data(); // 值区(I 采样 / Q 读回由外部做)
uint32_t slot_addr(uint32_t slot) const; // 槽 → 值区偏移(槽表两级)
int64_t reg(uint8_t i) const; // 当前帧寄存器
const uint8_t* cur_instr_bytes() const; // 当前指令首字节(单步反汇编用)
uint32_t cur_instr_len() const; // 当前指令长度(变长)
```
- 寄存器 ≤256、映像很小,快照拷贝成本可忽略。
@@ -52,71 +54,17 @@ const Snapshot& snapshot() const;
---
## 12.9 执行计划
## V2 落地状态
| 项 | 状态 |
|---|---|
| 变长取指(4/8/16/32B,前缀分区)+ 字节 pc | ✅ `Machine.cpp` exec_one |
| 槽表两级(slot → addr → 值区变宽紧凑)+ LOAD_OFF/STORE_OFF | ✅ |
| 栈方案 C(预分配 max_stack、帧头 12B、r0..r7 复制、深度 64 双检查) | ✅ do_call/do_ret |
| 19 类型 func 驱动(整数饱和 / 无符号 / F32 / F64 IEEE | ✅ exec_arith/exec_cmp |
| CAL 15 条(TON/TOF/TP/CTU/DCTU/CTD/DCTD/CTUD/DCTUD/R_TRIG/F_TRIG/SR/RS/PWM/RTC | ✅ do_cal |
| 故障(None/CycleLimit/StackOverflow/BadOp/BadSlot/BadConst | ✅ |
| 边沿缓冲 edge_prev_(每槽 2 字节,跨周期) | ✅ |
| 确定性:同一 I 序列跑两遍,数据区完全一致 | ✅ vm_test |
指令执行语义(帧/调用栈、指令表、定时器、故障、line1 推演)见 [`指令执行.md`](指令执行.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`
```cpp
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 → RETMAIN 帧跨周期保留)
// 可观察性(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`(指令定义)+ 自带 `vm::Image`.stb 读)
### 步骤 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
### 步骤 3CALL / RET 帧栈 + 故障(已落地)
- `CALL fn_id`:压新帧(复制 r0..r7)→ pc=0`RET`:复制 r0..r7 回调用方 → 弹栈(MAIN 的 RET = 周期结束)
- `cycle_limit` 超限(用例 6)、栈深 > 64、`BadOp` / `BadSlot` / `BadConst` → 周期中止返回故障
### 步骤 4:定时器与边沿(已落地)
- 执行时用映像 `dt_ms` 推进(每周期一次调用 = 推进一次),不读系统时钟
- TONin 真 → et += dt(到 pt 停)、q = et ≥ ptin 假 → et = 0
- TOFin 真 → q=1、et=0;掉电 → et += dt、et ≥ pt → q=0
- CTUcu 上升沿(**上次 cu 存 VM 侧 vector**,按实例基槽索引)→ cv+1r → 复位;q = cv ≥ pv
### 步骤 5:可观察性基础(已落地)
- `step()` / `pc` / `fn_id` / `cycle_count` / `call_depth` / `reg` 落基础实现;`snapshot()` 留 12.11 补全
### 步骤 6`vm_test`(已落地)
- 手工拼小映像(`isa` 编码函数):赋值、跳转、CALL/RET 帧复制、cycle_limit
- 编译器产物全链路:用例 01/02/04/05/07/09/13/15/17/18/20 跑周期断言寄存器与数据区
- 用例 06`cycle_limit` 打满 → `CycleLimit` 故障
- TONdt=10、pt=30 → 第 3 周期 q=1CTU:上升沿计数
- **确定性**:同一 I 序列跑两遍,数据区完全一致
### 步骤 7:验证 + 提交(已落地,ctest 14/14
- `cmake --build` + `ctest`(新增 `vm_cycles` 后 10/10 全绿);20 用例扫描:14 正例执行通过
+109 -101
View File
@@ -1,128 +1,136 @@
# 指令执行流程(12.9
# 指令执行流程(V2
`vm` 模块的指令执行语义:取指、译码、执行、故障。规范以 [`指令与映像.md`](../isa/指令与映像.md) 为准,本文描述**执行器怎么跑一条指令**。执行模型见 [`初步计划.md`](../初步计划.md) 第 2 节。
## 执行模型
```text
加载(Machine::create):解析 .stb(vm 自读实现)→ 型号匹配(内建 STATOR1)→ SHA-256 校验
加载(Machine::create):解析 .stb(vm 自读实现)→ 型号匹配(内建 STATOR2)→ SHA-256 校验
任一不符 → 直接拒绝(不创建机器)
值区(data_)取映像值段的写副本(周期内 FB 字段/全局可变)
边沿缓冲 edge_prev_(每槽 2 字节,跨周期保留)
栈区预分配 max_stack 字节(栈方案 C,编译期算出)
每个扫描周期(run_cycle):
1. pc = 0,从入口函数(MAIN开始逐条执行
1. cycle_count = 0pc = 0,从入口函数(MAIN)逐条执行
2. 执行到 MAIN 的 RET → 周期结束
3. I 采样 / Q 写回由 executor 按 sidecar 做,VM 只管数据区
4. 定时器在 CAL_* 指令执行时用映像 dt_ms 推进
3. 周期末清调用栈(保留栈底 MAIN 帧,sp 回退到 MAIN 帧顶
4. I 采样 / Q 写回由 executor 按 sidecar 做,VM 只管值区)
5. 定时器在 CAL 指令执行时用映像 dt_ms 推进;RTC 用累计周期数 × 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)
- **pc**:当前指令在函数字节码段内的**字节**下标(从 0 起)
- **取指**:函数表 `func_row(fn_id)``code_offset` + `code_len`(字节)→ pc 越界 → `BadOp``instr_len` 按前缀定长(4/8/16/32B)搬字节
- **译码**前缀分区 + `[func:4|空:4]` 字节1`op` 按前缀分区查名;寄存器越界按 `uses_*` 直推(RRR/CC/RR 等)→ `BadOp`
- **周期计数**:每条指令执行后 `cycle_count++`;超过映像头 `cycle_limit``CycleLimit`(周期中止)
- **pc 先指向下一条(字节)**;跳转指令再 `pc += off`(off 相对下一条,与编译端 patch_jump 一致)
## 帧与调用栈
## 帧与调用栈(栈方案 C
```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; // 返回目标函数(调用方)
};
// 栈区(stack_,预分配 max_stack 字节):
// [MAIN 帧][帧2][帧3]... 从低地址起连续压栈
// 每帧 = 12B 帧头 + nregs×8 寄存器文件
// 帧头:hdr[0]=fn_id hdr[1]=ret_pc(字节) hdr[2]=ret_fn_id
```
- 调用栈 `std::vector<Frame>`**深度上限 64**;超限`StackOverflow` 故障
- 调用栈深度**上限 64**`frames_.size() >= 64` 时 CALL → `StackOverflow`);同时 `sp_ + 帧大小 > max_stack``StackOverflow`(双检查)
- **MAIN 帧跨周期保留**(PROGRAM 变量状态持久);周期结束只清调用栈(保留栈底 MAIN 帧)
- 帧寄存器初值 0
### CALL / RET(调用约定,冻结于 12.8
### CALL / RET(调用约定)
```text
CALL fn_id:
1. 新帧:regs 大小 = callee 的 nregs,全部 0
2. 复制当前帧 r0..r7 → 新帧 r0..r7(实参已在调用点 MOVE 进 r1..r7
3. 新帧 ret_pc = 当前 pc + 1CALL 下一条)、ret_fn_id = 当前函数
2. 复制当前帧 r0..r7 → 新帧 r0..r7(实参已在调用点 MOVE 进 r1..r7——全宽复制
3. 新帧 ret_pc = 当前 pc已指向 CALL 下一条,字节)、ret_fn_id = 当前函数
4. 压栈,pc = 0fn_id = callee
RET:
1. 若当前是 MAIN(栈深 0)→ 周期结束
1. 若当前是 MAIN(栈深 1)→ 周期结束ended_ = true
2. 否则:复制当前帧 r0..r7 → 调用方帧 r0..r7r0 = 结果,r1..r7 原样返回)
3. 弹栈,pc = ret_pcfn_id = ret_fn_id
```
## 值表示(寄存器 / 槽统一约定
## 值表示(func 位段驱动
| 类型 | 表示 | 说明 |
|---|---|---|
| BOOL | `0` / `1` | 逻辑指令产出、字面量 TRUE=1 |
| INT | `int16_t` 符号扩展 | `sat_*` 返回 int16,赋给 int64 寄存器自动符号扩展 |
| TIME | 全 64 位 | 毫秒 |
| func | 语义 | 宽度 | 读值扩展 | 类型 |
|---|---|---|---|---|
| 0 | 无符号 | 1B | 高位清 0 | BOOL/BYTE 等 |
| 1 | 有符号 | 1B | 符号扩展 | SINT |
| 2 | 无符号 | 2B | 高位清 0 | WORD/UINT |
| 3 | 有符号 | 2B | 符号扩展 | INT |
| 4 | 无符号 | 4B | 高位清 0 | DWORD/UDINT |
| 5 | 有符号 | 4B | 符号扩展 | DINT |
| 6 | 无符号 | 8B | 原样 | LWORD/ULINT |
| 7 | 有符号 | 8B | 原样 | LINT |
| 8 | F32 | 4B | 位模式(int64 低 32 位) | REAL |
| 9 | F64 | 8B | 位模式原样 | LREAL |
推论:**全宽 int64 比较等价于各类型自身比较**INT 符号扩展后 -5 < 3 正确)。类型语义由编译期类型检查保证,VM 不查类型。
- 寄存器(int64)统一承载:窄类型符号扩展/清 0 后,**全宽 int64 比较等价于各类型自身比较**类型语义由编译期类型检查保证,VM 只按 func 解释
- TIME/DATE/TOD/DT 走 func 6/764 位整数值)
## 数据区访问(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) = rs8 字节原样
槽号 slot(指令操作数u32)→ 槽表条目(8Baddr:u32 + 预留:u32)→ 值区字节偏移 addr
LOAD rd, slot → rd = 读 value[addr .. addr+宽)(按 func 宽度 + 扩展
STORE slot, rs → 写 value[addr .. addr+宽) = rs(按 func 宽度截断
LOAD_OFF rd, slot, off → 读 value[addr+off .. ]FB 字段访问)
STORE_OFF slot, off, rs → 写 value[addr+off .. ]FB 字段访问)
```
`LOAD_I` / `LOAD_M` / `LOAD_GLOBAL` / `STORE_Q` / `STORE_M` / `STORE_GLOBAL` 执行逻辑完全相同(v1 单一数据区,操作码只作语义标签)
槽表条目数 = `n_slots`;槽号越界(`slot >= n_slots`)或地址 + 宽度越出值段 → `BadSlot`
## 指令执行表
| 指令 | 流程 |
|---|---|
| `MOVE rd, rs` | `reg[rd] = reg[rs]` |
| `LOADK rd, cid` | 查常量表 `const_entry(cid)`BOOL → 0/1INT → `(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` | 见定时器与边沿推进 |
| `MOVE rd, rs` | `reg[rd] = reg[rs]`func 0..3 限宽 1/2/4/8B,高位清 0 |
| `LOADK rd, cid` | 查常量表 `const_entry(cid)`(8B 原始值):按 func 读低 w 字节 + 扩展(同 read_valueF32/F64 位模式) |
| `NOT rd, rs` | **逻辑非**`reg[rd] = (reg[rs] == 0) ? 1 : 0`(恒产 0/1 |
| `AND/OR/XOR/NAND/NOR/XNOR rd, ra, rb` | **位串逐位**64 位整域);BOOL 输入 0/1 下与逻辑等价 |
| `ADD/SUB/MUL/DIV rd, ra, rb` | 按 func:整数饱和(__int128 中间量)、无符号、F32/F64 IEEE |
| `CMP_EQ/NE/LT/LE/GT/GE rd, ra, rb` | 按 func 符号性比较(浮点 IEEE)`1` / `0` |
| `NEG rd, rs` | func 8/9 浮点取负(位模式),整数取负 |
| `JMP off` | `pc += off` |
| `JT rd, off` / `JF rd, off` | `reg[rd] != 0`JT/ `== 0`JF)时 `pc += off` |
| `LOAD / STORE` | 见数据区访问(槽表两级) |
| `LOAD_OFF / STORE_OFF` | 见数据区访问(FB 字段) |
| `CAL fb_id, slot` | 见内置 FB15 条) |
| `CALL fn_id` / `RET` | 见帧与调用栈 |
| `NOP` | 空转 |
### 跳转语义
### 整数算术(除法 A:饱和 + 截断)
```text
指令执行后 pc 先 +1(指向下一条),跳转指令再 pc += off
⇒ 目标下标 = 当前 + 1 + off(与编译端 patch_jump 一致)
整数(func 0..7):sat_add/sat_sub/sat_mul/sat_div__int128 中间量,结果夹到类型范围)
sat_div:除零 → 返回被除数原值(不抛错);MIN / -1 → 夹到 MAX;其余截断向零
浮点(func 8/9):IEEE 754(除零 → inf;编译期类型检查保证使用域)
```
### 周期指令计数
## 内置 FBCAL15 条)
- 每条指令执行后 `cycle_count++`;超过映像头 `cycle_limit``CycleLimit` 故障(周期中止)
- `JMP` / `JT` / `JF` / `CALL` 只算 1 条(cycle_limit 以"执行的指令数"计)
`CAL fb_id, slot``slot` 是实例**槽号**(槽表条目指向实例基址);字段偏移**编译期对齐计算**(1/2/4/8),字段类型按 machine.toml `[[fb]]` 表。每次执行 = 推进一次,Δt = 映像头 `dt_ms`(不读系统时钟)。边沿检测的"上次输入"存 VM 侧 `edge_prev_`(每槽 2 字节),跨周期保留:
## 定时器与边沿推进(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/etCTU = cu/r/pv/q/cvCTD = cd/ld/pv/q/cvCTUD = cu/cd/r/lu/pv/qu/qd/cvR_TRIG/F_TRIG = clk/q)。每次执行 = 推进一次,Δt = 映像头 `dt_ms`(不读系统时钟)。边沿检测的"上次输入"存 VM 侧 `edge_prev_`(每实例基槽 2 字节),跨周期保留:
```text
CAL_TON
in 为真:et += dtet ≥ pt 时停在 pt);q = (et ≥ pt) ? 1 : 0
in 为假:et = 0q = 0
CAL_TOF
in 为真:q = 1et = 0
in 掉电:et += dtet ≥ pt 时 q = 0
CAL_TP(脉冲):
in 上升沿启动 PT 时长脉冲(期间 in 变化不影响)
计时中et += dtet ≥ pt q = 0、et 归零(脉冲结束);否则 q = 1
非计时:et = 0q = 0
CAL_CTUcu 上升沿 → cv += 1r → cv = 0q = (cv ≥ pv) ? 1 : 0
CAL_CTDcd 上升沿 → cv -= 1ld → cv = pvq = (cv ≤ 0) ? 1 : 0
CAL_CTUDcu 上升沿 → cv += 1cd 上升沿 → cv -= 1r → cv = 0lu → cv = pv
qu = (cv ≥ pv) ? 1 : 0qd = (cv ≤ 0) ? 1 : 0
CAL_R_TRIGq = clk 且上次 clk 假(上升沿)
CAL_F_TRIGq = !clk 且上次 clk 真(下降沿)
```
| fb_id | FB | 字段布局(偏移:类型) | 语义 |
|---|---|---|---|
| 0 | TON | in@0:Bpt@8:TIMEq@16:Bet@24:TIME | in 真:et += dtet ≥ pt 停在 pt);q = (et ≥ pt) |
| 1 | TOF | 同 TON | in 真:q=1、et=0in 掉电:et += dtet ≥ pt → q=0 |
| 2 | TP | 同 TON | in 上升沿启动 PT 时长脉冲;计时 et += dt,期满 q=0、et=0 |
| 3 | CTU | cu@0:Br@1:Bpv@2:INTq@4:Bcv@6:INT | cu 上升沿 cv += 1r → cv=0q = (cv ≥ pv) |
| 4 | DCTU | cu@0:Br@1:Bpv@4:DINTq@8:Bcv@12:DINT | 同 CTUDINT |
| 5 | CTD | cd@0:Bld@1:Bpv@2:INTq@4:Bcv@6:INT | cd 上升沿 cv -= 1ld → cv=pvq = (cv ≤ 0) |
| 6 | DCTD | 同 DCTU 布局 | 同 CTDDINT |
| 7 | CTUD | cu@0:Bcd@1:Br@2:Blu@3:Bpv@4:INTqu@6:Bqd@7:Bcv@8:INT | cu 上升沿 +1、cd 上升沿 -1r → 0lu → pvqu=(cv≥pv)、qd=(cv≤0) |
| 8 | DCTUD | cu@0cd@1r@2lu@3:Bpv@4:DINTqu@8qd@9:Bcv@12:DINT | 同 CTUDDINT |
| 9 | R_TRIG | clk@0:Bq@1:B | q = clk 且上次 clk 假(上升沿) |
| 10 | F_TRIG | clk@0:Bq@1:B | q = !clk 且上次 clk 真(下降沿) |
| 11 | SR | S1@0:BR@1:BQ1@2:B | **置位优先**S1 → Q1=1;否则 R → Q1=0 |
| 12 | RS | SET@0:BRESET1@1:BQ1@2:B | **复位优先**RESET1 → Q1=0;否则 SET → Q1=1 |
| 13 | PWM | in@0:Bpt@8:TIMEduty@16:REALq@20:Bet@24:TIME | in 真et += dt≥ pt 回绕);q = (et < pt × duty)in 假:et=0、q=0 |
| 14 | RTC | enable@0:Bdate@4:DATEtod@8:TOD | 虚拟时钟:累计周期数 × dt_ms(1970 纪元);enable 真时写 date/tod |
## 故障
@@ -130,38 +138,38 @@ CAL_F_TRIGq = !clk 且上次 clk 真(下降沿)
|---|---|---|
| `None` | — | 正常完成 |
| `CycleLimit` | 周期指令数 > `cycle_limit`(用例 6 | 中止,返回故障 |
| `StackOverflow` | 调用栈深度 > 64 | 中止 |
| `BadOp` | op ≥ 29 | 中止 |
| `BadSlot` | slot × 8 + 8 > 数据区长度 | 中止 |
| `BadConst` | const_id ≥ n_consts | 中止 |
| `StackOverflow` | 调用栈深度 > 64`sp_ + 帧大小 > max_stack` | 中止 |
| `BadOp` | pc 越界 / 指令截断 / 前缀 RESERVED / 寄存器越界 / CAL fb_id 未知 | 中止 |
| `BadSlot` | 槽号 ≥ n_slots,或地址 + 宽度越出值段 | 中止 |
| `BadConst` | LOADK const_id ≥ n_consts | 中止 |
## 示例:line1 MAIN 逐指令推演
## 示例:line1 MAIN 逐指令推演V2
映像:`dt_ms=10``cycle_limit=100000`数据区 56 字节(7 槽 × 8B,方案 a)。
映像`--disasm` 实取)`dt_ms=10``cycle_limit=100000`值区 7 字节;槽 0..4 → 值区 0..4(紧凑 U80=EmergencyStop、1=I0_0、2=I0_1、3=Q0_0、4=starter 实例)。
```text
初始:MAIN 帧 regs[13] 全 0;槽 0..6 全 0(初值 0
初始:MAIN 帧 regs[13] 全 0;槽 0..4 全 0(初值 0
假定 executor 已采样:槽 0 = EmergencyStop = 0、槽 1 = I0_0 = 1、槽 2 = I0_1 = 0
pc 指令 执行后
── ───────────────────── ──────────────────────────────
0 LOAD_I r8, 1 r8 = data[8..16) 低字节 = 1I0_0
1 STORE_GLOBAL r8, 4 槽 4starter.start= 1
2 LOAD_GLOBAL r9, 2 r9 = 0I0_1
3 STORE_GLOBAL r9, 5 槽 5starter.stop= 0
4 LOAD_GLOBAL r8, 8 内联体:r8 = 槽 4 = 1start
5 JF r8, +3 r8≠0 不跳;pc = 6
6 LOAD_GLOBAL r10, 9 r10 = 槽 5 = 0stop
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 = 0EmergencyStop
11 NOT r11, r12 r11 = 1
12 MOVE r8, r11 r8 = 1
13 STORE_GLOBAL r8, 10 槽 6starter.q= 1
14 LOAD_GLOBAL r8, 10 r8 = 槽 6 = 1
15 STORE_Q r8, 3 槽 3Q0_0= 1
16 RET 周期结束;executor 读槽 3 → 硬件
pc 指令 执行后
── ───────────────────── ──────────────────────────────
0x9c LOAD.U8 r8, s1 r8 = value[1] = 1I0_0
0xa4 STORE_OFF.U8 r8, s4, 0 starter.S1(实例基址 4 + 0= 1
0xb4 LOAD.U8 r9, s2 r9 = value[2] = 0I0_1
0xbc STORE_OFF.U8 r9, s4, 1 starter.R4 + 1= 0
0xcc LOAD_OFF.U8 r8, s4, 0 r8 = 1S1
0xdc JF r8, +24 r8≠0 不跳;pc = 0xe4 + 0
0xe4 LOAD_OFF.U8 r10, s4, 1 r10 = 0R
0xf4 NOT r9, r10 r9 = 1
0xf8 MOVE r8, r9 r8 = 1
0xfc JF r8, +16 r8≠0 不跳;pc = 0x104
0x104 LOAD.U8 r12, s0 r12 = value[0] = 0EmergencyStop
0x10c NOT r11, r12 r11 = 1
0x110 MOVE r8, r11 r8 = 1
0x114 STORE_OFF.U8 r8, s4, 2 starter.Q14 + 2= 1
0x124 LOAD_OFF.U8 r8, s4, 2 r8 = 1Q1
0x134 STORE.U8 r8, s3 Q0_0(槽 3= 1
0x13c RET 周期结束;executor 读槽 3 → 硬件
```
结果:`Q0_0 = 1`start=1、stop=0、急停=0 → 电机启动)——与 `EXPECTED.md` 真值表一致。
结果:`Q0_0 = 1`start=1、stop=0、急停=0 → 电机启动)——与 `EXPECTED.md` 真值表一致vm_test 94 checks + executor 端到端验证)