From 84de006665001d7fedfc758ecef8b56e26477c9a Mon Sep 17 00:00:00 2001 From: chentianya Date: Wed, 26 Aug 2026 10:40:20 +0800 Subject: [PATCH] =?UTF-8?q?=E9=98=B6=E6=AE=B55-2=EF=BC=9A=E6=96=87?= =?UTF-8?q?=E6=A1=A3=E6=94=B6=E5=B0=BE=E2=80=94=E2=80=94vm=20=E6=8C=87?= =?UTF-8?q?=E4=BB=A4=E6=89=A7=E8=A1=8C/=E6=89=AB=E6=8F=8F=E5=91=A8?= =?UTF-8?q?=E6=9C=9F=E3=80=81compiler=20=E5=AF=84=E5=AD=98=E5=99=A8?= =?UTF-8?q?=E7=A0=81/=E7=AC=A6=E5=8F=B7=E8=A1=A8/STCompiler=E8=AF=B4?= =?UTF-8?q?=E6=98=8E=E3=80=81executor=20=E5=85=A5=E5=8F=A3=E5=85=A8?= =?UTF-8?q?=E9=83=A8=E6=9B=B4=E6=96=B0=E4=B8=BA=20V2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Doc/compiler/STCompiler使用说明.md | 52 ++++--- Doc/compiler/寄存器码.md | 89 ++++++------ Doc/compiler/符号表与链接.md | 42 +++--- Doc/executor/执行器入口.md | 6 +- Doc/vm/扫描周期.md | 118 +++++----------- Doc/vm/指令执行.md | 210 +++++++++++++++-------------- 6 files changed, 242 insertions(+), 275 deletions(-) diff --git a/Doc/compiler/STCompiler使用说明.md b/Doc/compiler/STCompiler使用说明.md index 4a1c268..decc268 100644 --- a/Doc/compiler/STCompiler使用说明.md +++ b/Doc/compiler/STCompiler使用说明.md @@ -1,13 +1,13 @@ # STCompiler 使用说明 -把 ST 工程(`project.toml` + 若干 `.st`)编译成单一映像 `.stb`。执行由 `BytecodeExecutor`(12.9/12.10 实现)负责。 +把 ST 工程(`project.toml` + 若干 `.st`)编译成单一映像 `.stb`。执行由 `BytecodeExecutor` 负责。 ## 构建 ```bash cmake --preset gcc-debug # 配置 cmake --build --preset gcc-debug # 编译 -ctest --test-dir build/gcc-debug # 测试(当前 9 个用例) +ctest --test-dir build/gcc-debug # 测试(14/14 全绿) ``` 可执行文件:`build/gcc-debug/compiler/STCompiler` @@ -35,29 +35,39 @@ STCompiler .stb --disasm --machine ```bash $ STCompiler line1.stb --disasm --machine machine.toml -image: line1.stb (288 bytes, 2 functions, 4 globals, entry fn 1) - dt_ms=10 cycle_limit=100000 hash=0xc3fe4ae74ad45900 model=STATOR1 sha=ok +image: line1.stb (399 bytes) +header: + magic = 0x43545353 # 魔数 "STSC" + version = 2 # 格式版本(V2) + model = STATOR2 # 型号标识 + ... + n_slots = 5 # 槽表条目数 + values_size = 7 # 值段字节数 + max_stack = 116 # 栈区字节数(编译期算) + sha = ok # 文件尾 SHA-256 完整性 functions: - fn 0: nregs=8, offset=0, len=1 - 0x0080 RET - fn 1: nregs=13, offset=4, len=17 - 0x0084 LOAD_I r8, 1 - 0x0088 STORE_GLOBAL r8, 4 + fn 0: nregs=8, offset=0, len=4 + 0x0098 RET + fn 1: nregs=13, offset=4, len=164 + 0x009c LOAD.U8 r8, s1 + 0x00a4 STORE_OFF.U8 r8, s4, 0 ... -data (56 bytes): +slots: + [0] addr=0 +data (7 bytes): 0x0000: ... ``` 说明: -- 指令偏移是**文件绝对字节偏移**,可直接对照 `xxd line1.stb` -- 映像里**没有符号名**(函数表只有 fn_id,数据段无名字映射),因此按 `fn 0` / `fn 1`、槽号显示 +- 指令偏移是**文件绝对字节偏移**,可直接对照 `xxd line1.stb`(V2 指令变长 4/8/16/32B) +- 映像里**没有符号名**(函数表只有 fn_id,槽表/值段无名字映射),因此按 `fn 0` / `fn 1`、槽号显示;`--dump-map` 可打印变量/槽映射 - `model=` 显示型号标识(`machine.toml [meta]` 生成);`sha=ok|BAD` 显示文件尾 SHA-256 校验结果 - 文件损坏或打不开 → `error: ...` 退出码 1 ## 编译产物(.stb 加固) -- **型号标识[32]**:`[meta] name + version` 拼成(如 `"STATOR1"`),写进头偏移 72 +- **型号标识[32]**:`[meta] name + version` 拼成(如 `"STATOR2"`),写进头(V2 头 128B,`kOffModelId`=32) - **SHA-256 文件尾[32]**:对文件尾之前全部内容计算;compiler 写侧与 vm 读侧各实现一份 - 执行器读取时型号不匹配 / SHA 不符 → 直接拒绝(详见 `Doc/isa/指令与映像.md` 12.13 修订) @@ -73,10 +83,10 @@ files: hash: 0xc3fe4ae74ad45900 $ STCompiler examples/line1/project.toml -o line1.stb --machine compiler/machine.toml -compiled: line1.stb (288 bytes, 2 functions, 4 globals) +compiled: line1.stb (399 bytes, 2 functions, 4 globals) ``` -产物:`line1.stb`(288 字节:头 104 含型号标识 + 各段 + SHA-256 文件尾)+ `line1.runtime.toml`(sidecar)。指令见 `Doc/isa/指令与映像.md`,结构拆解见 `Doc/isa/stb文件格式.md`。 +产物:`line1.stb`(399 字节:头 128B 含型号标识 + 各段 + SHA-256 文件尾)+ `line1.runtime.toml`(sidecar)。指令见 `Doc/isa/指令与映像.md`,结构拆解见 `Doc/isa/stb文件格式.md`。 ## 工程文件(project.toml) @@ -117,15 +127,15 @@ bit = 2 **变量段**:`VAR` / `VAR_INPUT` / `VAR_OUTPUT` / `VAR_GLOBAL`(仅 `gvl.file` 顶层)/ `VAR_EXTERNAL`。 -**类型**:`BOOL` / `INT` / `TIME`;内建 FB 类型 `TON` / `TOF` / `TP` / `CTU` / `CTD` / `CTUD` / `R_TRIG` / `F_TRIG`(关键字,不可作变量名)。 +**类型**:19 种(`BOOL`/`SINT`/`INT`/`DINT`/`LINT`/`BYTE`/`WORD`/`DWORD`/`LWORD`/`USINT`/`UINT`/`UDINT`/`ULINT`/`REAL`/`LREAL`/`TIME`/`DATE`/`TOD`/`DT`);内建 FB 15 条(`TON`/`TOF`/`TP`/`CTU`/`DCTU`/`CTD`/`DCTD`/`CTUD`/`DCTUD`/`R_TRIG`/`F_TRIG`/`SR`/`RS`/`PWM`/`RTC`,关键字,不可作变量名;FB 名三层判定:toml fb 表 → 用户 FB → 类型名)。 **语句**:赋值 `:=`、`IF / ELSIF / ELSE / END_IF`、`WHILE / END_WHILE`、FB 调用 `fb(in := ..., ...);`、字段读 `fb.Q`。 -**表达式**:字面量(`TRUE`/`FALSE`、整数、`T#10ms` 式 TIME)、`NOT`、`AND`/`OR`(**短路**)、比较 `= <> < <= > >=`(不连锁)、算术 `+ - * /`、一元负号、函数调用 `Add(3, 4)`。 +**表达式**:字面量(`TRUE`/`FALSE`、整数、`T#10ms` 式 TIME、`R#`/`D#`/`TOD#`/`DT#` 浮点与日期)、`NOT`、`AND`/`OR`(**短路**)、比较 `= <> < <= > >=`(不连锁)、算术 `+ - * /`、一元负号、函数调用 `Add(3, 4)`。 **函数**:结果 = 函数名赋值(`Add := a + b;`);返回类型 `FUNCTION Add : INT`;调用约定:结果 r0、参数 r1..r7(最多 7 个输入,函数内只读)、变量与临时从 r8 起(见 `Doc/compiler/寄存器码.md`)。 -**限制(v1 明确不做)**:指针 / `REF` / `CLASS` / `ANY` / `VAR_IN_OUT`(语法层直接拒绝)、链式比较、FB 字段赋值、`REAL`、字符串、用户类型套娃、隐式类型转换、函数循环调用(链接层拒绝)、`FUNCTION` 写全局(类型检查层拒绝,含经 `VAR_EXTERNAL`)。 +**限制(v1 明确不做)**:指针 / `REF` / `CLASS` / `ANY` / `VAR_IN_OUT`(语法层直接拒绝)、链式比较、FB 字段赋值、字符串、用户类型套娃、**隐式类型转换**(类型检查层拒绝,字面量按目标类型适配)、函数循环调用(链接层拒绝)、`FUNCTION` 写全局(类型检查层拒绝,含经 `VAR_EXTERNAL`)。 ## 编译管线与错误类别 @@ -149,10 +159,10 @@ project.toml + .st - `examples/line1/`:最小闭环(MAIN + MotorStarter FB + I/O + 全局),给人看 - `tests/cases/01~20/`:20 个用例(每份 `project.toml` + `.st` + `EXPECTED.md`),编译结果按表 -- `tests/`:CTest(`compiler_toml` / `lexer_tokens` / `parser_syntax` / `linker_links` / `typecheck_types` / `codegen_slice1` / `machine_config` / `vm_cycles` / `cases_all` / `cli_*`) +- `tests/`:CTest 14 项(`compiler_toml` / `lexer_tokens` / `parser_syntax` / `linker_links` / `typecheck_types` / `codegen_slice1` / `isa_roundtrip` / `machine_config` / `vm_test` / `vm_cycles` / `cases_all` / `cli_*`) ## 当前状态 -- ✅ 可编译:14 个正例用例全部产出 `.stb`(含型号标识 + SHA-256);6 个负例按期望拒绝 -- ✅ 执行:`BytecodeExecutor` 加载 `.stb` 跑扫描周期(型号/SHA 校验、回放、单步) +- ✅ 可编译:14 个正例用例全部产出 `.stb`(V2 头 128B + 型号标识 + SHA-256);6 个负例按期望拒绝 +- ✅ 执行:`BytecodeExecutor` 加载 `.stb` 跑扫描周期(型号/SHA 校验、回放、单步),V2 槽表两级采样 - ✅ 配置驱动:指令 opcode / 类型元数据 / FB 布局来自 `compiler/machine.toml`(`--machine`) diff --git a/Doc/compiler/寄存器码.md b/Doc/compiler/寄存器码.md index 687f402..ad8665d 100644 --- a/Doc/compiler/寄存器码.md +++ b/Doc/compiler/寄存器码.md @@ -1,33 +1,33 @@ -# 寄存器码(12.8) +# 寄存器码(V2) -compiler 模块的代码生成。输入:`Project` + `Unit`(AST)+ `LinkResult`;输出:`.stb` 映像字节(`std::vector`)。全工程定案见 [`初步计划.md`](../初步计划.md) 12.8,指令/映像布局见 [`指令与映像.md`](../isa/指令与映像.md)。 +compiler 模块的代码生成。输入:`Project` + `Unit`(AST)+ `LinkResult`;输出:`.stb` 映像字节(`std::vector`)。全工程定案见 [`初步计划.md`](../初步计划.md) 12.8,指令/映像布局见 [`指令与映像.md`](../isa/指令与映像.md),槽表/值段格式见 [`stb文件格式.md`](../isa/stb文件格式.md)。 ## 做 / 不做 **做** - 寄存器分配:局部/变量固定寄存器,表达式临时值往后编号,`nregs` 写进函数头 -- 跳转先留洞、后回填;`AND` / `OR` **必须编成跳转**(短路),禁止两边都算 -- 常量表(`LOADK`)、全局/实例槽(`LOAD_GLOBAL` / `STORE_GLOBAL` 等) -- `CALL fn_id` 有界帧;用户 FB 调用**内联展开**;内建 `CAL_TON` / `CAL_TOF` / `CAL_CTU` -- 拼装映像:函数表 / 常量表 / 数据段 / 头(`cycle_limit`、`dt_ms`、工程哈希、入口) +- 跳转先留洞、后回填(字节单位,相对下一条);`AND` / `OR` **必须编成跳转**(短路),禁止两边都算 +- 常量表(`LOADK`,8B 原始值无 tag,按 func 解释)、全局/实例槽(`LOAD` / `STORE` / `LOAD_OFF` / `STORE_OFF`) +- `CALL fn_id` 有界帧;用户 FB 调用**内联展开**;内建 `CAL fb_id, 实例槽` +- 拼装映像:V2 头 128B / 函数表 / 常量表 / 字节码 / 槽表 / 值段 + SHA-256 尾部 +- `max_stack`:MAIN + FUNCTION 调用图 DAG 最坏路径 Σ(12 + nregs×8);递归 = 编译期报错 **不做(第一版)** - 图染色、SSA、生命周期分析、优化、异常表、闭包 -- 函数指针、间接跳转、`REAL` +- 函数指针、间接跳转、动态实例 -## 存储布局(v1 定案) +## 存储布局(V2 定案) | 数据 | 放哪 | 说明 | |---|---|---| | PROGRAM / FUNCTION 标量变量与临时 | **寄存器**(函数帧) | 每周期帧保留由 VM 负责(12.9) | -| FB 实例(任意 POU 内声明) | **数据区实例块**(绝对地址) | 状态跨周期持久 | -| 全局(含 I/Q/M) | 数据区(声明序) | 12.6 已定槽号 | +| 全局(含 I/Q/M)与 FB 实例 | **值区**(紧凑变宽,1/2/4/8 对齐) | 槽表条目(8B:addr:u32 + 预留)指到值区偏移;状态跨周期持久 | -- **数据区** = 全局块(声明序,**每槽 8 字节定宽**,见 [`指令与映像.md`](../isa/指令与映像.md))→ FB 实例块(按实例声明序,每字段一槽) -- **指令 slot = 槽号**(u16,0..65535);数据偏移 = 槽号 × 8;符号表 `address` 即指令 slot,无映射 -- 字段访问 `fb.Q`:`LOAD_GLOBAL rd, <实例基槽+字段序号>`(编译期算死);I/Q/M 用 `LOAD_I` / `STORE_Q` / `LOAD_M` / `STORE_M` 同槽号 +- **槽表两级**:指令操作数 = **槽号**(u32)→ 槽表条目 → 值区字节偏移;`slot_addr(slot)` 由 VM/Image 提供 +- 全局按 GVL 声明序编槽;**FB 实例 = 1 个槽表条目**,字段在实例基址上按偏移访问(字段偏移编译期按 1/2/4/8 对齐计算,字段类型 = machine.toml `[[fb]]` 表) +- 字段访问 `fb.Q`:`LOAD_OFF rd, <实例槽>, <偏移>` / `STORE_OFF <实例槽>, <偏移>, rs`(16B SLOT+off 指令);I/Q/M 同普通槽(`LOAD` / `STORE`) - 函数帧寄存器:`FUNCTION` 结果固定 **r0**,`VAR_INPUT` 从 r1 起按声明序,`VAR` 续后;PROGRAM / FB 变量从 r0 起按声明序 ### 调用约定(v1 冻结,VM 12.9 按此实现) @@ -37,7 +37,7 @@ compiler 模块的代码生成。输入:`Project` + `Unit`(AST)+ `LinkResu | 结果寄存器 | **r0**(FUNCTION 结果写入处) | | 参数寄存器 | **r1..r7**(最多 7 个输入,按声明序) | | 变量 / 临时 | **全部从 r8 起**(调用约定区不受覆盖) | -| `CALL` | VM 复制当前帧 **r0..r7 → 新帧 r0..r7** | +| `CALL` | VM 复制当前帧 **r0..r7 → 新帧 r0..r7**(全宽) | | `RET` | VM 复制当前帧 **r0..r7 → 调用方帧 r0..r7**(r0=结果,r1..r7 原样返回) | | 函数输入 | 函数体内**只读**(写输入 → codegen error) | @@ -53,9 +53,9 @@ compiler 模块的代码生成。输入:`Project` + `Unit`(AST)+ `LinkResu ## 表达式与语句的指令模式 ```text -字面量 LOADK rd, const_id -变量读 LOAD_GLOBAL/LOAD_I/LOAD_M rd, slot (或帧内 MOVE) -字段读 LOAD_GLOBAL rd, <基址+偏移> +字面量 LOADK rd, const_id (func = 语言类型;REAL/LREAL 位模式按目标类型装载) +变量读 LOAD rd, slot (帧内直接 MOVE) +字段读 LOAD_OFF rd, <实例槽>, <偏移> 函数调用 <实参求值 → 参数寄存器> CALL fn_id MOVE rd, r0 NOT NOT rd, t @@ -65,7 +65,7 @@ AND/OR 短路(必须跳转): L_end: 比较 CMP_xx rd, t1, t2 算术 ADD/SUB/MUL/DIV rd, t1, t2 -负号 SUB rd, r0, t (0 - t,r0 恒 0) +负号 NEG rd, t IF JF t, L_else JMP L_end @@ -74,13 +74,13 @@ IF JF t, L_else WHILE L_loop: JF t, L_end JMP L_loop L_end: FB 调用(用户,内联展开): - <每个实参求值 → 临时> STORE_GLOBAL <实例基址+字段偏移>, 临时 - <内联体:读输入字段、算输出、写输出字段> -内建 实参同左 → CAL_TON/CAL_TOF/CAL_CTU <实例偏移> + <每个实参求值 → 临时> STORE_OFF <实例槽>, <字段偏移>, 临时 + <内联体:LOAD_OFF 读输入字段、算输出、STORE_OFF 写输出字段> +内建 实参同左 → CAL , <实例槽> ``` -- 常量统一进常量表(BOOL / INT / TIME),`const_id` = 首次出现序 -- 空条件寄存器约定:`r0` 恒 0(`enc_jmp` 用),`SUB rd, r0, t` 实现取负 +- 常量统一进常量表(8B 原始值,位模式去重;BOOL / 整数 / 时间 / 浮点 / 日期),`const_id` = 首次出现序;`LOADK` 按 func 解释位模式 +- 指令 func 位段 = 语言类型对应(`func_of_name`:宽度 × 符号 + F32/F64) ## 用户 FB 调用内联(v1 定案) @@ -90,17 +90,23 @@ FB 体共享会与「绝对字段地址」冲突(不同实例基址不同) - 代价:字节码随调用点数膨胀;v1 规模可接受;以后可升级为「基址寄存器寻址」指令 - FB 无递归、无动态实例,内联安全 -## 映像拼装 +## 映像拼装(V2) ```text +头 128B:magic / version=2 / model=STATOR2 / cycle_limit / dt_ms / 工程哈希 / + entry_fn_id / n_globals / n_consts / n_funcs / 各段偏移 / + n_slots / values_size / meta / max_stack 函数表:收集序 fn_id → { nregs, code_offset(相对字节码段), code_len } -常量表:const_id → { tag, value } -数据段:全局 + 实例块(见上) -头:cycle_limit / dt_ms / 工程哈希(12.3)/ entry=MAIN fn_id / n_globals / n_i=n_q=n_m=0 +常量表:const_id → { value:u64 }(无 tag) +字节码:各函数指令连续 +槽表:每条目 8B(addr:u32 + 预留:u32) +值段:全局 + FB 实例紧凑变宽 +尾部:SHA-256(覆盖头部到值段末尾) ``` -- 用 `isa` 的 `enc_*` 编码指令、`ImageView` 校验回读 -- 工程哈希用 12.3 的 `compute_project_hash` +- 用 `isa` 的 `enc_*` 编码指令(变长 4/8/16/32B,前缀分区) +- 工程哈希用 12.3 的 `compute_project_hash`(FNV-1a 64) +- `--dump-map` 可打印变量/槽映射(对照源码 ↔ 反汇编) ## 错误 @@ -109,27 +115,14 @@ FB 体共享会与「绝对字段地址」冲突(不同实例基址不同) ```text codegen error: register overflow (>256) in function 'f' codegen error: constant table overflow -codegen error: slot out of range +codegen error: no storage for 'x' +codegen error: no instance 't' in 'MAIN' +codegen error: recursive CALL/FB nesting too deep ``` ## 完成标准 -1. `line1` 能写出映像(函数字节码 + 全局/FB 槽 + 工程哈希),`ImageView` 校验通过 -2. 跳转回填无悬空(disasm 抽查) +1. `line1` 能写出映像(函数字节码 + 槽表/值段 + 工程哈希 + SHA-256),`StbView` 校验通过 +2. 跳转回填无悬空(`--disasm` 抽查) 3. 用例 1~9、13、15、17、18、20 编译出映像;负例(10/11/12/14/16/19)仍在前层被拒 -4. 全部构建 + `ctest` 无回归 - ---- - -## 执行计划(8 片,每片验证编译产物) - -1. **`Codegen.h`(新建)+ `Codegen.cpp` 骨架**:帧/槽/常量表/指令缓冲/回填基础设施;`MOVE` `LOADK` `RET` → 用例 1、2 出映像 -2. **全局与 I/Q/M**:`LOAD_I` / `STORE_Q` / `LOAD_M` / `STORE_M` / `LOAD_GLOBAL` / `STORE_GLOBAL`(用例 9) -3. **`NOT` + 短路 `AND` / `OR`**(用例 3,断言产物含跳转) -4. **`CMP_*` + `JMP` / `JT` / `JF` → `IF`**(用例 4) -5. **`WHILE`**(用例 5;`cycle_limit` 进映像头) -6. **`ADD/SUB/MUL/DIV` + TIME 常量**(用例 7、8) -7. **`CALL fn_id` + 有界帧**(用例 13) -8. **FB 实例块 + 字段偏移;内联用户 FB;`CAL_TON` / `CAL_TOF` / `CAL_CTU`**(用例 15、17、18、20 line1 全链路) - -每片配 `codegen_test` 断言(nregs / 指令序列 disasm / 回填 / 常量表 / 数据区);最终 `ctest` 9/9。 +4. 全部构建 + `ctest` 无回归(14/14) diff --git a/Doc/compiler/符号表与链接.md b/Doc/compiler/符号表与链接.md index 90eceeb..35be9dd 100644 --- a/Doc/compiler/符号表与链接.md +++ b/Doc/compiler/符号表与链接.md @@ -12,18 +12,19 @@ compiler 模块的符号收集与链接。输入:`Project`(toml)+ 各 `.st - `FUNCTION` 标无状态;`FUNCTION_BLOCK` 必须先有实例才能调用 - `CALL` 目标解析成 `fn_id`(立即数),无函数指针 - 校验:同名全局、未声明 `io.var`、重复 POU 名、未声明 FB 实例、函数循环调用 → 失败 -- FB 实例布局:按类型算字段清单(输入/输出/内部/内建 8 个 FB 固定字段(12.11 扩充)) +- FB 实例布局:按类型算字段清单(输入/输出/内部;内建 FB 15 条字段来自 machine.toml `[[fb]]` 表,编译期按 1/2/4/8 对齐计算偏移) **不做(第一版)** - 寄存器分配(12.8)、类型检查(12.7) - 多 GVL 文件、I/Q/M 与全局分槽(v1 **单一数据区**,见下) -## 数据区定址(v1 定案) +## 数据区定址(V2 定案) -- **单一数据区**:全部全局(含 I/Q/M)按 `globals.st` 声明顺序编槽号 0..n-1 -- 映像头 `n_globals` = 声明总数;`n_i` / `n_q` / `n_m` = 0(第一版不拆,见 [`初步计划.md`](../初步计划.md) 5.1) -- `LOAD_I` / `STORE_Q` / `LOAD_GLOBAL` / `STORE_GLOBAL` 操作码不同,槽号指向同一数据区 +- **槽表两级**:全部全局(含 I/Q/M)与 FB 实例统一编槽号(0..n-1);槽表条目(8B:addr:u32 + 预留)指向**值区**字节偏移;值区按变量类型紧凑变宽(1/2/4/8 对齐) +- 全局按 `globals.st` 声明顺序编槽;**FB 实例 = 1 个槽表条目**,字段走 `LOAD_OFF` / `STORE_OFF` 实例槽 + 编译期算死的字段偏移 +- 映像头 `n_globals` = 全局声明总数;`n_i` / `n_q` / `n_m` = 0(第一版不拆,见 [`初步计划.md`](../初步计划.md) 5.1) +- 访问统一 `LOAD` / `STORE`(+ `LOAD_OFF` / `STORE_OFF`),无 I/Q/M 专用操作码 - io 绑定(toml)只负责 channel/bit → 槽号映射(sidecar),不创造变量 ## 符号表一行 @@ -70,20 +71,27 @@ struct Symbol { ## FB 实例布局 - 用户 FB:字段 = 全部 `VAR_INPUT` + `VAR_OUTPUT` + `VAR`(内部),按声明段序排 -- 内建(冻结,isa 规格;12.11 扩为 8 个): +- 内建 FB 15 条(`compiler/machine.toml` `[[fb]]` 表驱动,fb_id 0..14): -| FB | 字段(名 → 类型) | -|---|---| -| TON | in BOOL, pt TIME, q BOOL, et TIME | -| TOF | in BOOL, pt TIME, q BOOL, et TIME | -| TP | in BOOL, pt TIME, q BOOL, et TIME | -| CTU | cu BOOL, r BOOL, pv INT, q BOOL, cv INT | -| CTD | cd BOOL, ld BOOL, pv INT, q BOOL, cv INT | -| CTUD | cu BOOL, cd BOOL, r BOOL, lu BOOL, pv INT, qu BOOL, qd BOOL, cv INT | -| R_TRIG | clk BOOL, q BOOL | -| F_TRIG | clk BOOL, q BOOL | +| fb_id | FB | 字段(名 → 类型) | +|---|---|---| +| 0 | TON | in BOOL, pt TIME, q BOOL, et TIME | +| 1 | TOF | 同 TON | +| 2 | TP | 同 TON | +| 3 | CTU | cu BOOL, r BOOL, pv INT, q BOOL, cv INT | +| 4 | DCTU | cu BOOL, r BOOL, pv DINT, q BOOL, cv DINT | +| 5 | CTD | cd BOOL, ld BOOL, pv INT, q BOOL, cv INT | +| 6 | DCTD | cd BOOL, ld BOOL, pv DINT, q BOOL, cv DINT | +| 7 | CTUD | cu BOOL, cd BOOL, r BOOL, lu BOOL, pv INT, qu BOOL, qd BOOL, cv INT | +| 8 | DCTUD | cu BOOL, cd BOOL, r BOOL, lu BOOL, pv DINT, qu BOOL, qd BOOL, cv DINT | +| 9 | R_TRIG | clk BOOL, q BOOL | +| 10 | F_TRIG | clk BOOL, q BOOL | +| 11 | SR | S1 BOOL, R BOOL, Q1 BOOL | +| 12 | RS | SET BOOL, RESET1 BOOL, Q1 BOOL | +| 13 | PWM | in BOOL, pt TIME, duty REAL, q BOOL, et TIME | +| 14 | RTC | enable BOOL, date DATE, tod TOD | -- 实例布局 = 类型字段表的一份拷贝;字段偏移 = 字段序号 × 8(8 字节定宽槽) +- 实例布局 = 类型字段表的一份拷贝;字段偏移编译期按 1/2/4/8 对齐计算(字段类型决定宽度),实例 = 槽表 1 条 ## 错误 diff --git a/Doc/executor/执行器入口.md b/Doc/executor/执行器入口.md index 263f274..c0c2701 100644 --- a/Doc/executor/执行器入口.md +++ b/Doc/executor/执行器入口.md @@ -22,10 +22,10 @@ BytecodeExecutor .stb [--cycles N] [--replay ] [--step] ## 职责 -- 加载 `.stb` 映像与 `.runtime.toml` sidecar(I/O 绑定 var → 槽号 → channel/bit) +- 加载 `.stb` 映像与 `.runtime.toml` sidecar(I/O 绑定 var → 槽号 → channel/bit;V2 槽号为槽表条目下标) - 读取时**校验型号标识与 SHA-256**:型号不匹配 / 文件被篡改 → 直接报错拒绝(执行器只接受与自身内建型号一致的映像) -- 按 sidecar 采样 I、写回 Q(不创造变量) -- `dt_ms` / `cycle_limit` 从映像头取,推进内置 FB(TON/TOF/TP/CTU/CTD/CTUD/R_TRIG/F_TRIG) +- 按 sidecar 采样 I、写回 Q(不创造变量;V2 槽表两级:绑定 var → 槽号 → `slot_addr` → 值区) +- `dt_ms` / `cycle_limit` 从映像头取,推进内置 FB(15 条:TON/TOF/TP/CTU/DCTU/CTD/DCTD/CTUD/DCTUD/R_TRIG/F_TRIG/SR/RS/PWM/RTC) - 单步、看寄存器和映像、按录制的 `I` 回放 ## 边界 diff --git a/Doc/vm/扫描周期.md b/Doc/vm/扫描周期.md index 33c0bd1..277fc57 100644 --- a/Doc/vm/扫描周期.md +++ b/Doc/vm/扫描周期.md @@ -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 clock);RTC 用累计周期数 ``` ## 边界 -- 只认 `.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& 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`(指令定义)+ 自带 `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) - -### 步骤 3:CALL / RET 帧栈 + 故障(已落地) - -- `CALL fn_id`:压新帧(复制 r0..r7)→ pc=0;`RET`:复制 r0..r7 回调用方 → 弹栈(MAIN 的 RET = 周期结束) -- `cycle_limit` 超限(用例 6)、栈深 > 64、`BadOp` / `BadSlot` / `BadConst` → 周期中止返回故障 - -### 步骤 4:定时器与边沿(已落地) - -- 执行时用映像 `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`(已落地) - -- 手工拼小映像(`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:验证 + 提交(已落地,ctest 14/14) - -- `cmake --build` + `ctest`(新增 `vm_cycles` 后 10/10 全绿);20 用例扫描:14 正例执行通过 diff --git a/Doc/vm/指令执行.md b/Doc/vm/指令执行.md index f6ade00..a5f34f5 100644 --- a/Doc/vm/指令执行.md +++ b/Doc/vm/指令执行.md @@ -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 = 0;pc = 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 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`,**深度上限 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 + 1(CALL 的下一条)、ret_fn_id = 当前函数 + 2. 复制当前帧 r0..r7 → 新帧 r0..r7(实参已在调用点 MOVE 进 r1..r7)——全宽复制 + 3. 新帧 ret_pc = 当前 pc(已指向 CALL 下一条,字节)、ret_fn_id = 当前函数 4. 压栈,pc = 0,fn_id = callee RET: - 1. 若当前是 MAIN(栈深 0)→ 周期结束 + 1. 若当前是 MAIN(栈深 1)→ 周期结束(ended_ = true) 2. 否则:复制当前帧 r0..r7 → 调用方帧 r0..r7(r0 = 结果,r1..r7 原样返回) 3. 弹栈,pc = ret_pc,fn_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/7(64 位整数值) -## 数据区访问(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 字节原样) +槽号 slot(指令操作数,u32)→ 槽表条目(8B:addr: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/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` | 见定时器与边沿推进 | +| `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_value;F32/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` | 见内置 FB(15 条) | | `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;编译期类型检查保证使用域) ``` -### 周期指令计数 +## 内置 FB(CAL,15 条) -- 每条指令执行后 `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/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 字节),跨周期保留: - -```text -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 真(下降沿) -``` +| fb_id | FB | 字段布局(偏移:类型) | 语义 | +|---|---|---|---| +| 0 | TON | in@0:B,pt@8:TIME,q@16:B,et@24:TIME | in 真:et += dt(et ≥ pt 停在 pt);q = (et ≥ pt) | +| 1 | TOF | 同 TON | in 真:q=1、et=0;in 掉电:et += dt,et ≥ pt → q=0 | +| 2 | TP | 同 TON | in 上升沿启动 PT 时长脉冲;计时 et += dt,期满 q=0、et=0 | +| 3 | CTU | cu@0:B,r@1:B,pv@2:INT,q@4:B,cv@6:INT | cu 上升沿 cv += 1;r → cv=0;q = (cv ≥ pv) | +| 4 | DCTU | cu@0:B,r@1:B,pv@4:DINT,q@8:B,cv@12:DINT | 同 CTU(DINT) | +| 5 | CTD | cd@0:B,ld@1:B,pv@2:INT,q@4:B,cv@6:INT | cd 上升沿 cv -= 1;ld → cv=pv;q = (cv ≤ 0) | +| 6 | DCTD | 同 DCTU 布局 | 同 CTD(DINT) | +| 7 | CTUD | cu@0:B,cd@1:B,r@2:B,lu@3:B,pv@4:INT,qu@6:B,qd@7:B,cv@8:INT | cu 上升沿 +1、cd 上升沿 -1;r → 0;lu → pv;qu=(cv≥pv)、qd=(cv≤0) | +| 8 | DCTUD | cu@0,cd@1,r@2,lu@3:B,pv@4:DINT,qu@8,qd@9:B,cv@12:DINT | 同 CTUD(DINT) | +| 9 | R_TRIG | clk@0:B,q@1:B | q = clk 且上次 clk 假(上升沿) | +| 10 | F_TRIG | clk@0:B,q@1:B | q = !clk 且上次 clk 真(下降沿) | +| 11 | SR | S1@0:B,R@1:B,Q1@2:B | **置位优先**:S1 → Q1=1;否则 R → Q1=0 | +| 12 | RS | SET@0:B,RESET1@1:B,Q1@2:B | **复位优先**:RESET1 → Q1=0;否则 SET → Q1=1 | +| 13 | PWM | in@0:B,pt@8:TIME,duty@16:REAL,q@20:B,et@24:TIME | in 真:et += dt(≥ pt 回绕);q = (et < pt × duty);in 假:et=0、q=0 | +| 14 | RTC | enable@0:B,date@4:DATE,tod@8:TOD | 虚拟时钟:累计周期数 × dt_ms(1970 纪元);enable 真时写 date/tod | ## 故障 @@ -130,38 +138,38 @@ 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 | 中止 | +| `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(紧凑 U8:0=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) 低字节 = 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 → 硬件 +pc 指令 执行后 +── ───────────────────── ────────────────────────────── +0x9c LOAD.U8 r8, s1 r8 = value[1] = 1(I0_0) +0xa4 STORE_OFF.U8 r8, s4, 0 starter.S1(实例基址 4 + 0)= 1 +0xb4 LOAD.U8 r9, s2 r9 = value[2] = 0(I0_1) +0xbc STORE_OFF.U8 r9, s4, 1 starter.R(4 + 1)= 0 +0xcc LOAD_OFF.U8 r8, s4, 0 r8 = 1(S1) +0xdc JF r8, +24 r8≠0 不跳;pc = 0xe4 + 0 +0xe4 LOAD_OFF.U8 r10, s4, 1 r10 = 0(R) +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] = 0(EmergencyStop) +0x10c NOT r11, r12 r11 = 1 +0x110 MOVE r8, r11 r8 = 1 +0x114 STORE_OFF.U8 r8, s4, 2 starter.Q1(4 + 2)= 1 +0x124 LOAD_OFF.U8 r8, s4, 2 r8 = 1(Q1) +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 端到端验证)。