Compare commits

...
7 Commits
Author SHA1 Message Date
Admin 8c0fc4fe98 12.9 步骤 6:vm_test 固化(手工映像 + 编译器产物 + 确定性)。
- 手工映像:标量/跳转/数据区、CALL/RET 帧复制(r1/r2 实参 → r0 结果)、自调用 StackOverflow、越界槽 BadSlot、坏常量 BadConst
- 编译器产物:用例 01~09/13/15/17/18/20 跑周期断言(TON 第 3 周期 q=1、line1 真值表 4 组)
- 用例 06 CycleLimit;确定性:同一 I 序列两遍数据区逐字节一致
- step 单步:停在指令边界、cycle_count/regs 逐步核对
- ctest 10/10(vm_cycles 84 断言)
2026-08-21 20:22:36 +08:00
Admin ada9e5f99e 12.9 步骤 4:定时器 CAL_TON/CAL_TOF/CAL_CTU。
- do_cal:内建布局冻结(TON/TOF: in/pt/q/et、CTU: cu/r/pv/q/cv,字段=槽序号×8)
- TON:in 真 → et+=dt(到 pt 停)、q=et≥pt;假 → 清零
- TOF:in 真 → q=1/et=0;掉电 → et+=dt、et≥pt → q=0
- CTU:cu 上升沿(ctu_prev_ 跨周期)→ cv+1;r 复位;q=cv≥pv
- 抽 slot_get/slot_set 复用(exec_one 的 LOAD/STORE 同步重构)
- 验证:TON 3 周期到 q=1、TOF 掉电延时 2 周期、CTU 交替上升沿到 cv=3、复位;line1 三组 I 组合 Q 正确 + 确定性两遍一致
2026-08-21 20:20:26 +08:00
Admin a84a1a62ce 12.9 步骤 3:CALL/RET 帧栈与故障。
- do_call:压新帧(nregs 分配、复制 r0..r7、记 ret_pc/ret_fn_id)→ pc=0;fn_id 越界 BadOp、深度≥64 StackOverflow
- do_ret:复制 r0..r7 回调用方(r0=结果)→ 弹栈 → pc=ret_pc;MAIN 的 RET = 周期结束
- 验证:用例 13 x=Add(3,4)=7、双周期帧保留;手拼自调用映像触发 StackOverflow(深度 64 后清栈)
2026-08-21 20:17:55 +08:00
Admin efe4dd1a2c 12.9 步骤 2:VM 译码 switch(标量/跳转/LOAD-STORE)。
- Machine.cpp:create(拷贝数据段+建 MAIN 帧)、run_cycle、step、exec_one
- 标量:MOVE/LOADK(常量表 tag 定值表示)/NOT/AND/OR/四则饱和/CMP 全宽比较
- 跳转:JMP/JT/JF(相对下一条);LOAD/STORE:slot×8 定宽槽,BadSlot 校验
- 寄存器越界检查按指令形态(a|b 作偏移/槽号/常量 id 时不查)
- 修:ImageView 默认构造公开(无效态)、Codegen 两个编译警告
- 冒烟:用例 02/04/07/05 跑通、06 触发 CycleLimit、step 单步
2026-08-21 20:16:28 +08:00
Admin 815eb87a46 12.9 步骤 1:VM 头文件(API、Fault 枚举、帧结构)。
- Machine.h:create/run_cycle + 可观察性(step/pc/fn_id/cycle_count/call_depth/data/reg)
- Fault:None/CycleLimit/StackOverflow/BadOp/BadSlot/BadConst
- 帧 { fn_id, regs[nregs], ret_pc, ret_fn_id },调用栈深度 64,MAIN 帧跨周期保留
- ctu_prev_ 预留(步骤 4);exec_one/do_call/do_ret/do_cal 桩
2026-08-21 20:12:43 +08:00
Admin ce948451e0 12.9 步骤 0:数据段改 8 字节定宽槽(方案 a)。
- 指令与映像.md / 寄存器码.md:slot 语义 = 槽号(数据偏移 = 槽号 × 8),取消变宽对齐与 global_offset 映射
- Codegen.cpp:删除 width_of/align_up/global_offsets_;layout_data/layout_fb_instances 按槽号布局(字段 = 槽序号)
- stb文件格式.md:line1 真实字节更新(224B、56B 数据段、starter 槽 4/5/6)
- codegen_test:断言更新(槽号化 + 数据区字节数),189 断言 9/9 全绿
2026-08-21 20:01:21 +08:00
Admin cd417cd27b 12.9 执行计划写进扫描周期.md。
- 步骤 0:数据段 8 字节定宽槽(方案 a)——文档/codegen/测试三处落地
- 步骤 1~7:Machine.h API、译码 switch、CALL/RET 帧栈与故障、
  定时器 Δt 推进、可观察性基础、vm_test(用例 06/17/18/20 + 确定性)、验证提交
- 指令执行语义指向指令执行.md
2026-08-21 19:58:03 +08:00
11 changed files with 1113 additions and 124 deletions
+3 -3
View File
@@ -25,9 +25,9 @@ compiler 模块的代码生成。输入:`Project` + `Unit`AST+ `LinkResu
| FB 实例(任意 POU 内声明) | **数据区实例块**(绝对地址) | 状态跨周期持久 | | FB 实例(任意 POU 内声明) | **数据区实例块**(绝对地址) | 状态跨周期持久 |
| 全局(含 I/Q/M) | 数据区(声明序) | 12.6 已定槽号 | | 全局(含 I/Q/M) | 数据区(声明序) | 12.6 已定槽号 |
- **数据区** = 全局块(声明序,按对齐规则:BOOL 1B / INT 2B / TIME 8B,起点对齐自身宽度)→ FB 实例块(按实例声明序,各块按类型布局大小、8 字节对齐 - **数据区** = 全局块(声明序,**每槽 8 字节定宽**,见 [`指令与映像.md`](../isa/指令与映像.md))→ FB 实例块(按实例声明序,每字段一槽
- **指令 slot = 数据区字节偏移**u16);符号表 `address` 保持逻辑槽号(12.6 冻结),编译时做 槽号 → 偏移 映射 - **指令 slot = 槽号**u160..65535);数据偏移 = 槽号 × 8;符号表 `address` 即指令 slot,无映射
- 字段访问 `fb.Q``LOAD_GLOBAL rd, <实例基+字段偏移>`(编译期算死);I/Q/M 用 `LOAD_I` / `STORE_Q` / `LOAD_M` / `STORE_M` 同槽号 - 字段访问 `fb.Q``LOAD_GLOBAL rd, <实例基+字段序号>`(编译期算死);I/Q/M 用 `LOAD_I` / `STORE_Q` / `LOAD_M` / `STORE_M` 同槽号
- 函数帧寄存器:`FUNCTION` 结果固定 **r0**`VAR_INPUT` 从 r1 起按声明序,`VAR` 续后;PROGRAM / FB 变量从 r0 起按声明序 - 函数帧寄存器:`FUNCTION` 结果固定 **r0**`VAR_INPUT` 从 r1 起按声明序,`VAR` 续后;PROGRAM / FB 变量从 r0 起按声明序
### 调用约定(v1 冻结,VM 12.9 按此实现) ### 调用约定(v1 冻结,VM 12.9 按此实现)
+19 -16
View File
@@ -1,6 +1,6 @@
# .stb 映像文件结构 # .stb 映像文件结构
编译产物 `.stb` 的**面向使用者**的结构说明。规范以 [`指令与映像.md`](指令与映像.md) 为准,本文用 `line1.stb`184 字节)做真实拆解。 编译产物 `.stb` 的**面向使用者**的结构说明。规范以 [`指令与映像.md`](指令与映像.md) 为准,本文用 `line1.stb`224 字节)做真实拆解。
## 概览 ## 概览
@@ -74,15 +74,15 @@ line12 行):
- `CALL``fn_id` = 函数表行下标 - `CALL``fn_id` = 函数表行下标
- 调用约定:结果 r0、参数 r1..r7、变量与临时 r8 起 - 调用约定:结果 r0、参数 r1..r7、变量与临时 r8 起
line1 `MAIN` 前 5 条(`0x60` 起): line1 `MAIN` 前 5 条(`0x60`slot 为 8 字节定宽槽号):
| 偏移 | 字节 | 反汇编 | 含义 | | 偏移 | 字节 | 反汇编 | 含义 |
|---|---|---|---| |---|---|---|---|
| 0x60 | `12 08 01 00` | `LOAD_I r8, 1` | I0_0io.input 绑定 → LOAD_I | | 0x64 | `12 08 01 00` | `LOAD_I r8, 1` | I0_0io.input 绑定 → LOAD_I,槽 1 |
| 0x64 | `17 08 08 00` | `STORE_GLOBAL r8, 8` | starter.start ← I0_0 | | 0x68 | `17 08 04 00` | `STORE_GLOBAL r8, 4` | starter.start(槽 4← I0_0 |
| 0x68 | `16 09 02 00` | `LOAD_GLOBAL r9, 2` | I0_1 | | 0x6C | `16 09 02 00` | `LOAD_GLOBAL r9, 2` | I0_1(槽 2 |
| 0x6C | `17 09 09 00` | `STORE_GLOBAL r9, 9` | starter.stop ← I0_1 | | 0x70 | `17 09 05 00` | `STORE_GLOBAL r9, 5` | starter.stop(槽 5← I0_1 |
| 0x70 | `16 08 08 00` | `LOAD_GLOBAL r8, 8` | 内联体:读 starter.start | | 0x74 | `16 08 04 00` | `LOAD_GLOBAL r8, 4` | 内联体:读 starter.start(槽 4 |
## FB 布局段(`offset_fb` 起) ## FB 布局段(`offset_fb` 起)
@@ -90,18 +90,21 @@ v1 为空(用户 FB 内联展开,字段偏移已算进数据段;内建 TON
## 数据段(`offset_data` 起) ## 数据段(`offset_data` 起)
**全局块**`n_globals` 个,声明序)→ **FB 实例块**(按 POU 收集序、实例声明序,块间 8 字节对齐) **每槽 8 字节定宽**12.9 方案 a):`BOOL` 用低 1 字节、`INT` 用低 2 字节(小端)、`TIME` 全 8 字节。指令 `slot` 操作数即**槽号**,数据偏移 = 槽号 × 8
宽与对齐:`BOOL` 1 字节 / `INT` 2 字节 / `TIME` 8 字节,每个槽起点对齐自身宽度 序:**全局块**`n_globals` 个,声明序)→ **FB 实例块**(POU 收集序、实例声明序,每字段一槽)
line1 数据段(16 字节,`0xA8` 起): line1 数据段(56 字节 = 7 槽 × 8B`0xA8` 起):
| 偏移 | 内容 | | 槽号 | 偏移 | 内容 |
|---|---| |---|---|---|
| 0xA8..0xAB | 全局:EmergencyStop、I0_0、I0_1、Q0_0BOOL,初值 0 | | 0 | 0xA8 | EmergencyStopBOOL,初值 0 |
| 0xAC..0xAF | 填充(实例块 8 对齐 | | 1 | 0xB0 | I0_0BOOL |
| 0xB0..0xB2 | starter 实例:start、stop、qBOOL | | 2 | 0xB8 | I0_1BOOL |
| 0xB3..0xB7 | 填充 | | 3 | 0xC0 | Q0_0BOOL |
| 4 | 0xC8 | starter.startBOOL |
| 5 | 0xD0 | starter.stopBOOL |
| 6 | 0xD8 | starter.qBOOL |
## 用工具验证 ## 用工具验证
+2 -2
View File
@@ -76,7 +76,7 @@ C++20 定宽:
- 有符号:`TOF` 剩余时间、两个时间点相减会出负值,免去无符号边界处理 - 有符号:`TOF` 剩余时间、两个时间点相减会出负值,免去无符号边界处理
- 64 位:为将来 `DATE_AND_TIME`(毫秒时间戳)与 `TON` / `TOF` / `TP` 的 64 位定时器预留;毫秒量程约 2.9 亿年,扫描周期场景永不溢出 - 64 位:为将来 `DATE_AND_TIME`(毫秒时间戳)与 `TON` / `TOF` / `TP` 的 64 位定时器预留;毫秒量程约 2.9 亿年,扫描周期场景永不溢出
- 代价:槽位 8 字节对齐。**槽位对齐规则**`BOOL` 1 字节、`INT` 2 字节、`TIME` 8 字节;连续排列按类型对齐,需要时插 padding,偏移以本规则为准 - 代价:槽位 8 字节对齐。**槽位规则**(12.9 方案 a):每槽 8 字节定宽,`BOOL` 用低 1 字节、`INT` 用低 2 字节(小端)`TIME` 8 字节;槽号 × 8 = 数据偏移,无 padding
--- ---
@@ -180,7 +180,7 @@ FB 实例固定布局,字段偏移编译期算死。
### 数据段 ### 数据段
槽初值按序排:全局(`n_globals`)→ I → Q → M。槽宽与对齐`BOOL` 1 字节 / `INT` 2 字节 / `TIME` 8 字节,每个槽起点对齐到自身宽度(需要时插 padding) 槽初值按序排:全局(`n_globals`v1 不拆 I/Q/M)→ FB 实例块(每字段一槽)。**每槽 8 字节定宽**(12.9 方案 a)`BOOL` 用低 1 字节`INT` 用低 2 字节(小端)、`TIME` 8 字节。指令 `slot` 操作数即**槽号**,数据偏移 = 槽号 × 8;符号表 `address` 与指令 slot 一致,无映射
工程哈希:收录源文件路径排序后对内容做 **FNV-1a 64**,只保证回放槽位一致,不是密码学哈希。FNV-1a 64basis `0xcbf29ce484222325`prime `0x100000001b3` 工程哈希:收录源文件路径排序后对内容做 **FNV-1a 64**,只保证回放槽位一致,不是密码学哈希。FNV-1a 64basis `0xcbf29ce484222325`prime `0x100000001b3`
+71
View File
@@ -48,3 +48,74 @@ const Snapshot& snapshot() const;
- 寄存器 ≤256、映像很小,快照拷贝成本可忽略。 - 寄存器 ≤256、映像很小,快照拷贝成本可忽略。
- 单步、回放、dump 都建立在同一套观察 API 上。 - 单步、回放、dump 都建立在同一套观察 API 上。
---
## 12.9 执行计划
指令执行语义(帧/调用栈、指令表、定时器、故障、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``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
### 步骤 3CALL / 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` 推进(每周期一次调用 = 推进一次),不读系统时钟
- 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``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` 故障
- TONdt=10、pt=30 → 第 3 周期 q=1CTU:上升沿计数
- **确定性**:同一 I 序列跑两遍,数据区完全一致
### 步骤 7:验证 + 提交
- `cmake --build` + `ctest`(新增 `vm_cycles` 后 10/10 全绿);20 用例扫描:14 正例执行通过
+38 -83
View File
@@ -7,15 +7,14 @@
* @details 设计说明(详见 Doc/compiler/寄存器码.md): * @details 设计说明(详见 Doc/compiler/寄存器码.md):
* - 切片 1~6:帧/字面量/MOVE/RET;全局数据区;短路 AND/ORCMP 与 IFWHILE 与四则; * - 切片 1~6:帧/字面量/MOVE/RET;全局数据区;短路 AND/ORCMP 与 IFWHILE 与四则;
* FUNCTION 与 CALL(调用约定 r0/r1..r7/r8+)。 * FUNCTION 与 CALL(调用约定 r0/r1..r7/r8+)。
* - 切片 7:FB 实例 → 数据区实例块(跨周期持久);字段地址 = 实例基 + 字段偏移 * - 切片 7:FB 实例 → 数据区实例块(跨周期持久);字段槽号 = 实例基 + 字段序号
* (编译期算死);字段读写走 LOAD_GLOBAL / STORE_GLOBAL * (编译期算死);字段读写走 LOAD_GLOBAL / STORE_GLOBAL
* 内建 TON/TOF/CTU → 实参写字段后 CAL_* <实例偏移> * 内建 TON/TOF/CTU → 实参写字段后 CAL_* <实例基槽>
* 用户 FB → 调用点内联展开(实参写字段 → FB 体以实例字段为变量编译) * 用户 FB → 调用点内联展开(实参写字段 → FB 体以实例字段为变量编译)
* - 其余构造报 codegen error * - 方案 a(12.9 前置):数据段**每槽 8 字节定宽**,指令 slot = 槽号(无映射)
* *
* 函数清单: * 函数清单:
* - put_le32 / put_le64 小端写入映像缓冲 * - put_le32 / put_le64 小端写入映像缓冲
* - align_up / width_of 对齐与槽宽
* - Builder::Builder (构造)存工程/源文件/链接结果/输出,收集 io 绑定分类 * - Builder::Builder (构造)存工程/源文件/链接结果/输出,收集 io 绑定分类
* - Builder::run 布局数据区(全局 + 实例)→ 逐 POU 建函数 → 拼映像 * - Builder::run 布局数据区(全局 + 实例)→ 逐 POU 建函数 → 拼映像
* - Builder::fail 组装 "codegen error: <msg>" 返回 false * - Builder::fail 组装 "codegen error: <msg>" 返回 false
@@ -29,9 +28,10 @@
* - store_target 赋值左值:STORE_* 到全局槽(或内联模式实例字段) * - store_target 赋值左值:STORE_* 到全局槽(或内联模式实例字段)
* - compile_expr 表达式编译到寄存器(含 Field 字段读) * - compile_expr 表达式编译到寄存器(含 Field 字段读)
* - cmp_op / arith_op / load_op / store_op 操作码选择 * - cmp_op / arith_op / load_op / store_op 操作码选择
* - global_offset / instance_of 槽号 → 偏移 / 实例 → 字段地址 * - global_offset 槽号恒等(方案 a 无映射)
* - instance_of 实例 → 字段槽号表
* - patch_jump 回填跳转偏移(相对下一条指令) * - patch_jump 回填跳转偏移(相对下一条指令)
* - layout_data / layout_fb_instances / init_of 数据区布局 * - layout_data / layout_fb_instances / init_of 数据区布局8 字节定宽槽)
* - const_id 取常量表 id(无则追加) * - const_id 取常量表 id(无则追加)
* - assemble_image 拼头 + 常量表 + 函数表 + 字节码 + 数据段 * - assemble_image 拼头 + 常量表 + 函数表 + 字节码 + 数据段
* - codegen_project 对外入口 * - codegen_project 对外入口
@@ -68,38 +68,6 @@ namespace {
} }
} }
/**
* @brief 按 2 的幂宽度向上对齐
* @param v 当前偏移
* @param w 对齐宽度(1/2/8
* @return 对齐后的偏移
*/
uint32_t align_up(uint32_t v, uint32_t w) {
return (v + w - 1) & ~(w - 1);
}
/**
* @brief 类型名 → 槽宽(对齐规则见 Doc/isa/指令与映像.md
* @param name "bool" / "int" / "time"
* @return 1 / 2 / 8;未知返回 1
*/
uint32_t width_of(const std::string& name) {
if (name == "int") return 2;
if (name == "time") return 8;
return 1; // bool 及未知
}
/**
* @brief TypeKind → 槽宽(FB 字段布局用)
* @param k 类型种类
* @return 1 / 2 / 8;未知返回 1
*/
uint32_t width_of(TypeKind k) {
if (k == TypeKind::Int) return 2;
if (k == TypeKind::Time) return 8;
return 1;
}
/** /**
* @brief 代码生成器(切片 1) * @brief 代码生成器(切片 1)
*/ */
@@ -274,9 +242,6 @@ namespace {
if (d.type.kind == TypeKind::FbUser || d.type.kind == TypeKind::FbBuiltin) { if (d.type.kind == TypeKind::FbUser || d.type.kind == TypeKind::FbBuiltin) {
continue; continue;
} }
if (r > 255) {
return fail("register overflow in function '" + pou.name + "'");
}
f->regs[d.name] = r++; f->regs[d.name] = r++;
} }
} }
@@ -692,8 +657,8 @@ namespace {
case BinOp::Le: return isa::Op::CMP_LE; case BinOp::Le: return isa::Op::CMP_LE;
case BinOp::Gt: return isa::Op::CMP_GT; case BinOp::Gt: return isa::Op::CMP_GT;
case BinOp::Ge: return isa::Op::CMP_GE; case BinOp::Ge: return isa::Op::CMP_GE;
default: return isa::Op::CMP_EQ; // Add/Sub/Mul/Div 不经此函数
} }
return isa::Op::CMP_EQ;
} }
/** /**
@@ -715,58 +680,56 @@ namespace {
} }
/** /**
* @brief 逻辑槽号 → 数据区字节偏移(layout_data 先行 * @brief 逻辑槽号(符号表 address)即指令 slot(方案 a:8 字节定宽,无映射
* @param slot 全局逻辑槽号(符号表 address * @param slot 全局逻辑槽号
* @return 数据区字节偏移(u16 可容 * @return 同值(u16 可容,layout_data 已校验
*/ */
uint16_t global_offset(uint32_t slot) const { uint16_t global_offset(uint32_t slot) const {
return static_cast<uint16_t>(global_offsets_[slot]); return static_cast<uint16_t>(slot);
} }
/** /**
* @brief 布局全局数据区:对齐 + 初值字节 * @brief 布局全局数据区:每槽 8 字节定宽(方案 a
* @details 槽 = 声明序12.6);对齐规则 BOOL 1B / INT 2B / TIME 8B * @details 槽 = 符号表 address声明序12.6);
* 起点对齐自身宽度;初值取自 GVL 声明的 has_init(无则 0 * BOOL 用低 1 字节、INT 用低 2 字节(小端)、TIME 全 8 字节;
* @return true 成功;false(槽溢出等,err 已写 * 初值取自 GVL 声明的 has_init(无则 0
* @return true 成功;false(槽号越界,err 已写)
*/ */
bool layout_data() { bool layout_data() {
uint32_t cur = 0;
for (const Symbol& s : link_.globals) { for (const Symbol& s : link_.globals) {
const uint32_t w = width_of(s.type_name); if (s.address > 0xFFFF) {
cur = align_up(cur, w);
if (cur + w > 0xFFFF) {
return fail("data area exceeds slot range"); return fail("data area exceeds slot range");
} }
global_offsets_.push_back(cur);
bool has_init = false; bool has_init = false;
int64_t init = 0; int64_t init = 0;
init_of(s.name, &has_init, &init); init_of(s.name, &has_init, &init);
data_.resize(cur + w, 0); const int64_t v = has_init ? init : 0;
if (s.type_name == "bool") { const size_t off = static_cast<size_t>(s.address) * 8;
data_[cur] = static_cast<uint8_t>(has_init ? init : 0); data_.resize(off + 8, 0);
} else if (s.type_name == "int") { if (s.type_name == "int") {
const uint16_t v = static_cast<uint16_t>(has_init ? init : 0); const uint16_t iv = static_cast<uint16_t>(v);
data_[cur] = static_cast<uint8_t>(v & 0xFFu); data_[off] = static_cast<uint8_t>(iv & 0xFFu);
data_[cur + 1] = static_cast<uint8_t>((v >> 8) & 0xFFu); data_[off + 1] = static_cast<uint8_t>((iv >> 8) & 0xFFu);
} else if (s.type_name == "time") { } else if (s.type_name == "time") {
const uint64_t v = static_cast<uint64_t>(has_init ? init : 0); const uint64_t tv = static_cast<uint64_t>(v);
for (int i = 0; i < 8; ++i) { for (int i = 0; i < 8; ++i) {
data_[cur + i] = static_cast<uint8_t>((v >> (8 * i)) & 0xFFu); data_[off + i] = static_cast<uint8_t>((tv >> (8 * i)) & 0xFFu);
} }
} else { // bool
data_[off] = v ? 1 : 0;
} }
cur += w;
} }
return true; return true;
} }
/** /**
* @brief 布局 FB 实例块:数据区全局之后,按 POU 收集序 / 实例声明序 * @brief 布局 FB 实例块:数据区全局之后,按 POU 收集序 / 实例声明序
* @details 每实例:字段按布局段序对齐排布(BOOL 1 / INT 2 / TIME 8), * @details 每实例:字段按布局段序各占一槽(8 字节定宽),
* 字段地址 = 实例基 + 字段偏移;实例块间 8 字节对齐,块内容初值 0 * 字段地址 = 实例基 + 字段序号;实例基槽 = 累计槽数
* @return true 成功;falseerr 已写) * @return true 成功;falseerr 已写)
*/ */
bool layout_fb_instances() { bool layout_fb_instances() {
uint32_t cur = align_up(static_cast<uint32_t>(data_.size()), 8); uint32_t cur = static_cast<uint32_t>(data_.size() / 8); // 槽号起点
bool any = false; bool any = false;
for (const LinkResult::PouScope& sc : link_.scopes) { for (const LinkResult::PouScope& sc : link_.scopes) {
for (const Symbol& s : sc.syms) { for (const Symbol& s : sc.syms) {
@@ -777,27 +740,20 @@ namespace {
if (lay == sc.fb_instances.end()) { if (lay == sc.fb_instances.end()) {
return fail("no layout for instance '" + s.name + "'"); return fail("no layout for instance '" + s.name + "'");
} }
cur = align_up(cur, 8);
InstFields inst; InstFields inst;
inst.base = cur; inst.base = cur; // 实例基槽(CAL_* 操作数)
inst.type_name = s.type_name; inst.type_name = s.type_name;
uint32_t off = 0; for (size_t i = 0; i < lay->second.fields.size(); ++i) {
for (const FbField& fd : lay->second.fields) { inst.field_addr[lay->second.fields[i].name] = cur + i;
const uint32_t w = width_of(fd.type);
off = align_up(off, w);
inst.field_addr[fd.name] = cur + off;
off += w;
} }
cur += align_up(off, 8); cur += static_cast<uint32_t>(lay->second.fields.size());
instances_[sc.name + "/" + s.name] = inst; instances_[sc.name + "/" + s.name] = inst;
any = true; any = true;
} }
} }
// 仅在有实例时把数据区补到实例区终点(无实例则保持全局区原样) // 仅在有实例时把数据区补到实例区终点(无实例则保持全局区原样)
if (any) { if (any) {
while (data_.size() < cur) { data_.resize(static_cast<size_t>(cur) * 8, 0);
data_.push_back(0);
}
} }
return true; return true;
} }
@@ -945,9 +901,8 @@ namespace {
std::vector<isa::ConstEntry> consts_; std::vector<isa::ConstEntry> consts_;
std::map<std::string, bool> io_input_; // io.input 绑定名(小写) std::map<std::string, bool> io_input_; // io.input 绑定名(小写)
std::map<std::string, bool> io_output_; // io.output 绑定名(小写) std::map<std::string, bool> io_output_; // io.output 绑定名(小写)
std::vector<uint32_t> global_offsets_; // 逻辑槽号 → 数据区字节偏移 std::vector<uint8_t> data_; // 数据区(8 字节定宽槽:全局 + FB 实例块)
std::vector<uint8_t> data_; // 数据区(全局初值 + FB 实例块) std::map<std::string, InstFields> instances_; // "POU/实例名" → 字段槽号
std::map<std::string, InstFields> instances_; // "POU/实例名" → 字段地址
const InstFields* inline_fields_ = nullptr; // 内联 FB 体字段上下文(可空) const InstFields* inline_fields_ = nullptr; // 内联 FB 体字段上下文(可空)
}; };
+3 -1
View File
@@ -77,6 +77,8 @@ namespace isa {
// 只读视图:校验过的映像 // 只读视图:校验过的映像
class ImageView { class ImageView {
public: public:
// 默认构造为无效态(ok() == falseerror() == "uninitialized"
ImageView();
static ImageView from(const uint8_t* buf, size_t len); static ImageView from(const uint8_t* buf, size_t len);
static ImageView from(const std::vector<uint8_t>& buf); static ImageView from(const std::vector<uint8_t>& buf);
@@ -96,7 +98,7 @@ namespace isa {
size_t data_len() const; // 字节数 size_t data_len() const; // 字节数
private: private:
ImageView(); // 私有成员与内部构造辅助(from 使用)
const uint8_t* buf_; const uint8_t* buf_;
size_t len_; size_t len_;
+11
View File
@@ -78,3 +78,14 @@ target_compile_definitions(codegen_test PRIVATE
add_test(NAME codegen_slice1 add_test(NAME codegen_slice1
COMMAND codegen_test) COMMAND codegen_test)
# VM 测试:手工映像 + 编译器产物 + 定时器 + 确定性(REPO_ROOT 注入源目录绝对路径)
add_executable(vm_test
./src/vm_test.cpp)
target_link_libraries(vm_test PRIVATE compiler vm)
target_compile_definitions(vm_test PRIVATE
REPO_ROOT="${CMAKE_SOURCE_DIR}")
add_test(NAME vm_cycles
COMMAND vm_test)
+17 -17
View File
@@ -174,7 +174,7 @@ static bool test_case09() {
const isa::ImageView v = isa::ImageView::from(img); const isa::ImageView v = isa::ImageView::from(img);
CHECK(v.ok()); CHECK(v.ok());
CHECK(v.header().n_globals == 1); CHECK(v.header().n_globals == 1);
CHECK(v.data_len() == 2); // INT 2 字节 CHECK(v.data_len() == 8); // 8 字节定宽槽
CHECK(v.data_bytes()[0] == 5); // G1 初值 5(小端低位) CHECK(v.data_bytes()[0] == 5); // G1 初值 5(小端低位)
CHECK(v.data_bytes()[1] == 0); CHECK(v.data_bytes()[1] == 0);
@@ -248,14 +248,14 @@ static bool test_layout() {
const isa::ImageView v = isa::ImageView::from(img); const isa::ImageView v = isa::ImageView::from(img);
CHECK(v.ok()); CHECK(v.ok());
CHECK(v.header().n_globals == 3); CHECK(v.header().n_globals == 3);
CHECK(v.data_len() == 16); // 0 + 2 + 8(对齐)= 16 CHECK(v.data_len() == 24); // 3 槽 × 8 字节定宽
CHECK(v.data_bytes()[0] == 0); // B 无初值 CHECK(v.data_bytes()[0] == 0); // B 无初值
CHECK(v.data_bytes()[2] == 0x2C && v.data_bytes()[3] == 0x01); // I = 300 CHECK(v.data_bytes()[8] == 0x2C && v.data_bytes()[9] == 0x01); // I = 300(槽 1
CHECK(v.data_bytes()[8] == 10); // T = 10ms CHECK(v.data_bytes()[16] == 10); // T = 10ms(槽 2
char buf[64]; char buf[64];
isa::disasm(reinterpret_cast<const uint32_t*>(v.code_bytes())[0], buf, sizeof buf); isa::disasm(reinterpret_cast<const uint32_t*>(v.code_bytes())[0], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOAD_GLOBAL r8, 2") == 0); // x := IINT 偏移 2 CHECK(std::strcmp(buf, "LOAD_GLOBAL r8, 1") == 0); // x := I槽 1
return true; return true;
} }
@@ -552,7 +552,7 @@ static bool test_case15() {
CHECK(v.ok()); CHECK(v.ok());
CHECK(v.header().n_funcs == 2); // FB 占位 + MAIN CHECK(v.header().n_funcs == 2); // FB 占位 + MAIN
CHECK(v.header().entry_fn_id == 1); CHECK(v.header().entry_fn_id == 1);
CHECK(v.data_len() == 8); // 实例 3Bstart@0 stop@1 q@2),8 对齐 CHECK(v.data_len() == 24); // 实例 3× 8 字节定宽
const isa::FuncRow fm = v.func_row(1); const isa::FuncRow fm = v.func_row(1);
CHECK(fm.nregs == 12); CHECK(fm.nregs == 12);
@@ -602,11 +602,11 @@ static bool test_case17() {
isa::disasm(ins[2], buf, sizeof buf); isa::disasm(ins[2], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOADK r10, 1") == 0); CHECK(std::strcmp(buf, "LOADK r10, 1") == 0);
isa::disasm(ins[3], buf, sizeof buf); isa::disasm(ins[3], buf, sizeof buf);
CHECK(std::strcmp(buf, "STORE_GLOBAL r10, 8") == 0); // ptTIME 8 对齐 CHECK(std::strcmp(buf, "STORE_GLOBAL r10, 1") == 0); // pt槽 1
isa::disasm(ins[4], buf, sizeof buf); isa::disasm(ins[4], buf, sizeof buf);
CHECK(std::strcmp(buf, "CAL_TON 0") == 0); CHECK(std::strcmp(buf, "CAL_TON 0") == 0);
isa::disasm(ins[5], buf, sizeof buf); isa::disasm(ins[5], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOAD_GLOBAL r8, 16") == 0); // t.Q CHECK(std::strcmp(buf, "LOAD_GLOBAL r8, 2") == 0); // t.Q(槽 2
return true; return true;
} }
@@ -619,7 +619,7 @@ static bool test_case18() {
const isa::ImageView v = isa::ImageView::from(img); const isa::ImageView v = isa::ImageView::from(img);
CHECK(v.ok()); CHECK(v.ok());
CHECK(v.data_len() == 40); // tf 块 0..31in@0 pt@8 q@16 et@24)、c 块 32.. CHECK(v.data_len() == 72); // tf 4 槽 + c 5 槽 = 9 槽 × 8
const isa::FuncRow r = v.func_row(0); const isa::FuncRow r = v.func_row(0);
CHECK(r.nregs == 14); CHECK(r.nregs == 14);
const uint32_t* ins = reinterpret_cast<const uint32_t*>(v.code_bytes()); const uint32_t* ins = reinterpret_cast<const uint32_t*>(v.code_bytes());
@@ -627,11 +627,11 @@ static bool test_case18() {
isa::disasm(ins[4], buf, sizeof buf); isa::disasm(ins[4], buf, sizeof buf);
CHECK(std::strcmp(buf, "CAL_TOF 0") == 0); CHECK(std::strcmp(buf, "CAL_TOF 0") == 0);
isa::disasm(ins[12], buf, sizeof buf); isa::disasm(ins[12], buf, sizeof buf);
CHECK(std::strcmp(buf, "CAL_CTU 32") == 0); CHECK(std::strcmp(buf, "CAL_CTU 4") == 0);
isa::disasm(ins[13], buf, sizeof buf); isa::disasm(ins[13], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOAD_GLOBAL r9, 36") == 0); // c.Q CHECK(std::strcmp(buf, "LOAD_GLOBAL r9, 7") == 0); // c.Q(槽 7
isa::disasm(ins[14], buf, sizeof buf); isa::disasm(ins[14], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOAD_GLOBAL r10, 38") == 0); // c.CVINT 2 对齐 CHECK(std::strcmp(buf, "LOAD_GLOBAL r10, 8") == 0); // c.CV槽 8
return true; return true;
} }
@@ -660,7 +660,7 @@ static bool test_line1() {
CHECK(v.header().n_globals == 4); CHECK(v.header().n_globals == 4);
CHECK(v.header().n_funcs == 2); CHECK(v.header().n_funcs == 2);
CHECK(v.header().entry_fn_id == 1); CHECK(v.header().entry_fn_id == 1);
CHECK(v.data_len() == 16); // 全局 4B + 实例块 8..10(8 对齐) CHECK(v.data_len() == 56); // 7 槽 × 8 字节定宽
const isa::FuncRow fm = v.func_row(1); const isa::FuncRow fm = v.func_row(1);
CHECK(fm.nregs == 13); CHECK(fm.nregs == 13);
@@ -670,15 +670,15 @@ static bool test_line1() {
isa::disasm(im[0], buf, sizeof buf); isa::disasm(im[0], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOAD_I r8, 1") == 0); // I0_0io.input CHECK(std::strcmp(buf, "LOAD_I r8, 1") == 0); // I0_0io.input
isa::disasm(im[1], buf, sizeof buf); isa::disasm(im[1], buf, sizeof buf);
CHECK(std::strcmp(buf, "STORE_GLOBAL r8, 8") == 0); // starter.start CHECK(std::strcmp(buf, "STORE_GLOBAL r8, 4") == 0); // starter.start(槽 4
isa::disasm(im[2], buf, sizeof buf); isa::disasm(im[2], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOAD_GLOBAL r9, 2") == 0); // I0_1 CHECK(std::strcmp(buf, "LOAD_GLOBAL r9, 2") == 0); // I0_1
isa::disasm(im[3], buf, sizeof buf); isa::disasm(im[3], buf, sizeof buf);
CHECK(std::strcmp(buf, "STORE_GLOBAL r9, 9") == 0); // starter.stop CHECK(std::strcmp(buf, "STORE_GLOBAL r9, 5") == 0); // starter.stop(槽 5
isa::disasm(im[13], buf, sizeof buf); isa::disasm(im[13], buf, sizeof buf);
CHECK(std::strcmp(buf, "STORE_GLOBAL r8, 10") == 0); // starter.Q CHECK(std::strcmp(buf, "STORE_GLOBAL r8, 6") == 0); // starter.Q(槽 6
isa::disasm(im[14], buf, sizeof buf); isa::disasm(im[14], buf, sizeof buf);
CHECK(std::strcmp(buf, "LOAD_GLOBAL r8, 10") == 0); // starter.Q 读回 CHECK(std::strcmp(buf, "LOAD_GLOBAL r8, 6") == 0); // starter.Q 读回
isa::disasm(im[15], buf, sizeof buf); isa::disasm(im[15], buf, sizeof buf);
CHECK(std::strcmp(buf, "STORE_Q r8, 3") == 0); // Q0_0io.output CHECK(std::strcmp(buf, "STORE_Q r8, 3") == 0); // Q0_0io.output
isa::disasm(im[16], buf, sizeof buf); isa::disasm(im[16], buf, sizeof buf);
+440
View File
@@ -0,0 +1,440 @@
/**
* @file vm_test.cpp
* @brief VM 12.9 + + +
* @author
* @date 2026-08-21
*/
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>
#include "compiler/Codegen.h"
#include "compiler/Linker.h"
#include "compiler/Project.h"
#include "compiler/Typecheck.h"
#include "isa/Encode.h"
#include "isa/Image.h"
#include "isa/Instr.h"
#include "isa/Op.h"
#include "vm/Machine.h"
#ifndef REPO_ROOT
#define REPO_ROOT "."
#endif
static int g_checks = 0;
#define CHECK(cond) \
do { \
if (!(cond)) { \
std::printf("FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \
return false; \
} \
++g_checks; \
} while (0)
// ---- 数据区槽访问 ----
static int64_t slot(const vm::Machine& m, int s) {
int64_t v = 0;
for (int i = 0; i < 8; ++i) {
v |= static_cast<int64_t>(m.data()[s * 8 + i]) << (8 * i);
}
return v;
}
static void wslot(vm::Machine& m, int s, int64_t v) {
for (int i = 0; i < 8; ++i) {
m.data()[s * 8 + i] = static_cast<uint8_t>((v >> (8 * i)) & 0xFF);
}
}
// ---- 编译器产物全链路(编译 → 建机 → 跑周期)----
static bool make_machine(const char* dir, vm::Machine* m, std::string* err) {
using namespace compiler;
const std::string toml = std::string(REPO_ROOT) + "/" + dir + "/project.toml";
Project p;
if (!parse_project(toml, &p, err)) {
return false;
}
std::vector<SourceUnit> units;
for (const std::string& f : compile_files(p)) {
SourceUnit u;
if (!load_unit(p.base_dir + "/" + f, &u, err)) {
return false;
}
units.push_back(std::move(u));
}
LinkResult link;
if (!link_project(p, units, &link, err)) {
return false;
}
if (!check_project(p, units, link, err)) {
return false;
}
std::vector<uint8_t> img;
if (!codegen_project(p, units, link, &img, err)) {
return false;
}
return vm::Machine::create(img, m, err);
}
// ---- 手工拼映像 ----
struct HFunc {
std::vector<isa::Instr> code;
uint32_t nregs = 8;
};
static std::vector<uint8_t> hand_image(const std::vector<isa::ConstEntry>& consts,
const std::vector<HFunc>& funcs,
uint32_t entry = 0,
uint32_t cycle_limit = 100000,
const std::vector<uint8_t>& data = {}) {
size_t code_total = 0;
for (const HFunc& f : funcs) {
code_total += f.code.size() * 4;
}
const uint32_t off_const = 72;
const uint32_t off_funcs = off_const + static_cast<uint32_t>(consts.size()) * 12;
const uint32_t off_code = off_funcs + static_cast<uint32_t>(funcs.size()) * 12;
const uint32_t off_data = off_code + static_cast<uint32_t>(code_total);
const uint32_t off_end = off_data + static_cast<uint32_t>(data.size());
std::vector<uint8_t> b(off_end, 0);
auto put32 = [&](size_t o, uint32_t v) {
b[o + 0] = static_cast<uint8_t>(v & 0xFFu);
b[o + 1] = static_cast<uint8_t>((v >> 8) & 0xFFu);
b[o + 2] = static_cast<uint8_t>((v >> 16) & 0xFFu);
b[o + 3] = static_cast<uint8_t>((v >> 24) & 0xFFu);
};
auto put64 = [&](size_t o, uint64_t v) {
for (int i = 0; i < 8; ++i) {
b[o + i] = static_cast<uint8_t>((v >> (8 * i)) & 0xFFu);
}
};
put32(0, isa::kMagic);
put32(4, isa::kVersion);
put32(8, cycle_limit);
put32(12, 10); // dt_ms
put32(24, entry);
put32(44, static_cast<uint32_t>(consts.size()));
put32(48, static_cast<uint32_t>(funcs.size()));
put32(52, off_const);
put32(56, off_funcs);
put32(60, off_code);
put32(64, off_data);
put32(68, off_data);
for (size_t i = 0; i < consts.size(); ++i) {
const size_t o = off_const + i * 12;
put32(o, static_cast<uint32_t>(consts[i].tag));
put64(o + 4, consts[i].value);
}
size_t acc = 0;
for (size_t i = 0; i < funcs.size(); ++i) {
const size_t o = off_funcs + i * 12;
put32(o, funcs[i].nregs);
put32(o + 4, static_cast<uint32_t>(acc));
put32(o + 8, static_cast<uint32_t>(funcs[i].code.size()));
acc += funcs[i].code.size() * 4;
}
size_t c = off_code;
for (const HFunc& f : funcs) {
for (const isa::Instr in : f.code) {
put32(c, in);
c += 4;
}
}
for (size_t i = 0; i < data.size(); ++i) {
b[off_data + i] = data[i];
}
return b;
}
// ---- 1. 手工映像:标量 + 跳转 + 数据区 ----
static bool test_hand_scalar() {
// r8 := 5r9 := r8 + 3;槽 0 ← r9;槽 1 → r10;JT 跳过一条
const std::vector<isa::ConstEntry> consts = {
{isa::types::Int, 5},
{isa::types::Int, 3},
{isa::types::Int, 1},
};
const std::vector<HFunc> funcs = {{
{
isa::enc_imm(isa::Op::LOADK, 8, 0),
isa::enc_rr(isa::Op::MOVE, 9, 8),
isa::enc_imm(isa::Op::LOADK, 11, 1),
isa::enc_rrr(isa::Op::ADD, 9, 9, 11),
isa::enc_slot(isa::Op::STORE_GLOBAL, 9, 0),
isa::enc_slot(isa::Op::LOAD_GLOBAL, 10, 0),
isa::enc_jc(isa::Op::JT, 9, 1), // r9≠0 → 跳过下一条
isa::enc_imm(isa::Op::LOADK, 10, 2),
isa::enc_ret(),
},
12,
}};
const std::vector<uint8_t> img = hand_image(consts, funcs, 0, 100000,
std::vector<uint8_t>(16, 0)); // 2 槽
vm::Machine m;
std::string err;
CHECK(vm::Machine::create(img, &m, &err));
CHECK(m.run_cycle() == vm::Fault::None);
CHECK(m.reg(8) == 5);
CHECK(m.reg(9) == 8); // 5 + 3
CHECK(slot(m, 0) == 8); // 槽 0 收到 8
CHECK(m.reg(10) == 8); // 槽 1 读回(JT 跳过覆盖)
CHECK(m.cycle_count() == 8);
return true;
}
// ---- 2. 手工映像:CALL/RET 帧复制 ----
static bool test_hand_call() {
// fn0(入口):r8=5、r2=7、实参 r1←r8CALL 1;结果 r0 → r8
// fn1r0 = r1 + r2r1/r2 来自调用约定区复制)
const std::vector<isa::ConstEntry> consts = {
{isa::types::Int, 5},
{isa::types::Int, 7},
};
const std::vector<HFunc> funcs = {
{{
isa::enc_imm(isa::Op::LOADK, 8, 0),
isa::enc_imm(isa::Op::LOADK, 2, 1),
isa::enc_rr(isa::Op::MOVE, 1, 8),
isa::enc_call(1),
isa::enc_rr(isa::Op::MOVE, 8, 0),
isa::enc_ret(),
},
12},
{{
isa::enc_rrr(isa::Op::ADD, 0, 1, 2),
isa::enc_ret(),
},
10},
};
const std::vector<uint8_t> img = hand_image(consts, funcs);
vm::Machine m;
std::string err;
CHECK(vm::Machine::create(img, &m, &err));
CHECK(m.run_cycle() == vm::Fault::None);
CHECK(m.reg(8) == 12); // 5 + 7
CHECK(m.call_depth() == 1);
return true;
}
// ---- 3. 手工映像:自调用 → StackOverflow ----
static bool test_stack_overflow() {
const std::vector<HFunc> funcs = {{
{
isa::enc_call(0),
isa::enc_ret(),
},
8,
}};
const std::vector<uint8_t> img = hand_image({}, funcs);
vm::Machine m;
std::string err;
CHECK(vm::Machine::create(img, &m, &err));
CHECK(m.run_cycle() == vm::Fault::StackOverflow);
CHECK(m.call_depth() == 1); // 周期结束已清栈(留 MAIN
return true;
}
// ---- 4. 手工映像:越界槽 → BadSlot ----
static bool test_bad_slot() {
const std::vector<HFunc> funcs = {{
{
isa::enc_slot(isa::Op::STORE_GLOBAL, 8, 60000), // 数据区 0 字节
isa::enc_ret(),
},
12,
}};
const std::vector<uint8_t> img = hand_image({}, funcs);
vm::Machine m;
std::string err;
CHECK(vm::Machine::create(img, &m, &err));
CHECK(m.run_cycle() == vm::Fault::BadSlot);
return true;
}
// ---- 5. 编译器产物正例 ----
static bool test_cases_positive() {
std::string err;
vm::Machine m;
// 01 空 MAIN
CHECK(make_machine("tests/cases/01_empty_main", &m, &err));
CHECK(m.run_cycle() == vm::Fault::None);
// 02 BOOL 赋值:a=TRUE(r8) b=FALSE(r9)
CHECK(make_machine("tests/cases/02_bool_assign", &m, &err));
CHECK(m.run_cycle() == vm::Fault::None);
CHECK(m.reg(8) == 1 && m.reg(9) == 0);
// 03 短路:a=0 b=0 → x=0
CHECK(make_machine("tests/cases/03_short_circuit", &m, &err));
CHECK(m.run_cycle() == vm::Fault::None);
CHECK(m.reg(10) == 0);
// 04 IFsel=0 → out=10
CHECK(make_machine("tests/cases/04_if_elsif_else", &m, &err));
CHECK(m.run_cycle() == vm::Fault::None);
CHECK(m.reg(9) == 10);
// 05 WHILEn=10
CHECK(make_machine("tests/cases/05_while_normal", &m, &err));
CHECK(m.run_cycle() == vm::Fault::None);
CHECK(m.reg(8) == 10);
// 07 算术:a=42 b=10 c=32 eq=1
CHECK(make_machine("tests/cases/07_int_arith", &m, &err));
CHECK(m.run_cycle() == vm::Fault::None);
CHECK(m.reg(8) == 42 && m.reg(9) == 10 && m.reg(10) == 32 && m.reg(11) == 1);
// 08 TIME 字面量:t1=10 t2=1250
CHECK(make_machine("tests/cases/08_time_literal", &m, &err));
CHECK(m.run_cycle() == vm::Fault::None);
CHECK(m.reg(8) == 10 && m.reg(9) == 1250);
// 09 GVL + EXTERNALx = G1 初值 5(槽 0
CHECK(make_machine("tests/cases/09_gvl_external", &m, &err));
CHECK(m.run_cycle() == vm::Fault::None);
CHECK(m.reg(8) == 5);
// 13 FUNCTIONx = Add(3,4) = 7
CHECK(make_machine("tests/cases/13_function_call", &m, &err));
CHECK(m.run_cycle() == vm::Fault::None);
CHECK(m.reg(8) == 7);
// 15 FB 内联:starter(start=TRUE, stop=FALSE) → q=TRUE
CHECK(make_machine("tests/cases/15_fb_instance", &m, &err));
CHECK(m.run_cycle() == vm::Fault::None);
CHECK(m.reg(8) == 1);
// 17 TONdt=10、pt=30in 恒真 → 第 3 周期 q=1(槽 2)
CHECK(make_machine("tests/cases/17_ton", &m, &err));
m.run_cycle();
m.run_cycle();
CHECK(m.run_cycle() == vm::Fault::None);
CHECK(slot(m, 2) == 1); // t.Q
CHECK(slot(m, 3) == 30); // t.ET
// 18 TOF/CTU:跑一个周期无故障
CHECK(make_machine("tests/cases/18_tof_ctu", &m, &err));
CHECK(m.run_cycle() == vm::Fault::None);
// 20 line1I 组合 → Q0_0 真值表(槽 0=急停 1=I0_0 2=I0_1 3=Q0_0
CHECK(make_machine("tests/cases/20_line1", &m, &err));
struct {
int start, stop, es, expect;
} table[] = {
{1, 0, 0, 1},
{1, 1, 0, 0},
{0, 0, 0, 0},
{1, 0, 1, 0},
};
for (const auto& t : table) {
wslot(m, 0, t.es);
wslot(m, 1, t.start);
wslot(m, 2, t.stop);
CHECK(m.run_cycle() == vm::Fault::None);
CHECK(slot(m, 3) == t.expect);
}
return true;
}
// ---- 6. 用例 06cycle_limit 打满 ----
static bool test_cycle_limit() {
std::string err;
vm::Machine m;
CHECK(make_machine("tests/cases/06_while_cycle_limit", &m, &err));
CHECK(m.run_cycle() == vm::Fault::CycleLimit);
return true;
}
// ---- 7. 确定性:同一 I 序列跑两遍,数据区一致 ----
static bool test_determinism() {
std::string err;
vm::Machine a, b;
CHECK(make_machine("tests/cases/20_line1", &a, &err));
CHECK(make_machine("tests/cases/20_line1", &b, &err));
const int seq[4][3] = {{1, 0, 0}, {1, 1, 0}, {0, 0, 0}, {1, 0, 1}};
for (int rep = 0; rep < 2; ++rep) {
vm::Machine& m = rep ? b : a;
for (const auto& s : seq) {
wslot(m, 0, s[2]);
wslot(m, 1, s[0]);
wslot(m, 2, s[1]);
CHECK(m.run_cycle() == vm::Fault::None);
}
}
// 两遍结束:数据区逐字节一致(含 starter 实例状态)
CHECK(a.data_len() == b.data_len());
for (size_t i = 0; i < a.data_len(); ++i) {
if (a.data()[i] != b.data()[i]) {
std::printf("FAIL determinism byte %zu: %d vs %d\n", i, a.data()[i], b.data()[i]);
return false;
}
}
++g_checks;
return true;
}
// ---- 8. step 单步:停在指令边界 ----
static bool test_step() {
std::string err;
vm::Machine m;
CHECK(make_machine("tests/cases/02_bool_assign", &m, &err));
// 3 条指令:LOADK ×2 + RET
CHECK(m.step());
CHECK(m.cycle_count() == 1 && m.reg(8) == 1);
CHECK(m.step());
CHECK(m.cycle_count() == 2 && m.reg(9) == 0);
CHECK(!m.step()); // RET → 周期结束
CHECK(m.cycle_count() == 3);
CHECK(!m.step()); // 结束后不再执行
return true;
}
// ---- 9. 手工映像:坏常量 id → BadConst ----
static bool test_bad_const() {
const std::vector<HFunc> funcs = {{
{
isa::enc_imm(isa::Op::LOADK, 8, 99), // 常量表为空
isa::enc_ret(),
},
12,
}};
const std::vector<uint8_t> img = hand_image({}, funcs);
vm::Machine m;
std::string err;
CHECK(vm::Machine::create(img, &m, &err));
CHECK(m.run_cycle() == vm::Fault::BadConst);
return true;
}
int main() {
if (!test_hand_scalar()) return 1;
if (!test_hand_call()) return 1;
if (!test_stack_overflow()) return 1;
if (!test_bad_slot()) return 1;
if (!test_cases_positive()) return 1;
if (!test_cycle_limit()) return 1;
if (!test_determinism()) return 1;
if (!test_step()) return 1;
if (!test_bad_const()) return 1;
std::printf("vm_test: %d checks passed\n", g_checks);
return 0;
}
+115
View File
@@ -0,0 +1,115 @@
/**
* @file Machine.h
* @brief 12.9
* @author
* @date 2026-08-21
*
* @details Doc/vm/.md Doc/vm/.md
* - isa ImageView GC线
* - MAIN pc=0 RETMAIN 64
* - { fn_id, regs[nregs], ret_pc, ret_fn_id }CALL r0..r7
* RET r0=
* - 8 a slot = = slot × 8
* - v1 step / pc / fn_id / cycle_count / call_depth / reg
*/
#pragma once
#include <cstdint>
#include <string>
#include <vector>
#include "isa/Image.h"
namespace vm {
// 周期/指令故障
enum class Fault {
None, // 正常
CycleLimit, // 本周期指令数超过映像头 cycle_limit(用例 6
StackOverflow, // 调用栈深度超过 64
BadOp, // 非法操作码(op ≥ 29
BadSlot, // slot × 8 + 8 越出数据区
BadConst, // const_id 越出常量表
};
class Machine {
public:
/**
* @brief + + MAIN
* @param image .stb
* @param out Machine
* @param err nullptr
* @return true falseerr
*/
static bool create(const std::vector<uint8_t>& image, Machine* out,
std::string* err);
/**
* @brief MAIN pc=0 RET
* @return None = MAIN
*/
Fault run_cycle();
// ---- 可观察性(v1 预留,12.11 供单步/回放/TUI----
/**
* @brief
* @return true false MAIN RET
*/
bool step();
/** @brief 最近一次故障(step 返回 false 后查询) */
Fault fault() const;
/** @brief 当前指令下标(相对当前函数字节码段) */
uint32_t pc() const;
/** @brief 当前函数 fn_id */
uint32_t fn_id() const;
/** @brief 本周期已执行指令数 */
uint32_t cycle_count() const;
/** @brief 调用栈深度(MAIN 帧 = 1 */
uint32_t call_depth() const;
/** @brief 数据区(I 采样写入 / Q 读回由外部做;8 字节定宽槽) */
uint8_t* data();
const uint8_t* data() const;
/** @brief 数据区字节数 */
size_t data_len() const;
/** @brief 当前帧寄存器值(i < nregs */
int64_t reg(uint8_t i) const;
private:
struct Frame {
uint32_t fn_id = 0; // 本帧函数
std::vector<int64_t> regs; // 寄存器文件(大小 = 函数头 nregs)
uint32_t ret_pc = 0; // CALL 的下一条(非 MAIN 帧有意义)
uint32_t ret_fn_id = 0; // 返回目标函数
};
isa::ImageView image_; // 映像只读视图
std::vector<uint8_t> data_; // 数据区工作副本(8 字节定宽槽)
std::vector<Frame> frames_; // 调用栈([0] = MAIN,跨周期保留)
std::vector<uint8_t> ctu_prev_; // CTU 上次 cu(按实例基槽索引,12.9 步骤 4)
uint32_t pc_ = 0; // 当前 pc(相对本帧函数字节码段)
uint32_t cycle_count_ = 0; // 本周期指令数
Fault fault_ = Fault::None; // 最近一次故障
bool ended_ = false; // step 后周期是否已结束
Frame& cur_frame();
const Frame& cur_frame() const;
bool exec_one(); // 译码执行一条(12.9 步骤 2/3/4 填充)
Fault do_call(uint32_t fn_id); // 压帧;BadFn 用 BadOp 表示
Fault do_ret(); // 弹帧 / 周期结束
Fault do_cal(uint8_t op, uint16_t slot); // TON/TOF/CTU12.9 步骤 4
// 数据区槽读写(8 字节定宽;越界返回 false)
bool slot_get(uint16_t slot, int64_t* out) const;
bool slot_set(uint16_t slot, int64_t v);
};
}
+394 -2
View File
@@ -1,8 +1,400 @@
/** /**
* @file Machine.cpp * @file Machine.cpp
* @brief 12.9 * @brief 12.9 2 switch
* @date 2026-08-19 * @author
* @date 2026-08-21
*
* @details Doc/vm/.md
* - 2 create / run_cycle / step + MOVE/LOADK/NOT/AND/OR//CMP
* JMP/JT/JFLOAD/STOREslot × 8RET MAIN
* - 3 CALL/RET / 4 CAL_*
* - code_offset u32 pc +1 pc += off
* - 8 = slot × 8
*/ */
#include "vm/Machine.h"
#include <algorithm>
#include "isa/Encode.h"
#include "isa/Instr.h"
#include "isa/Op.h"
#include "isa/Types.h"
namespace vm { namespace vm {
bool Machine::create(const std::vector<uint8_t>& image, Machine* out,
std::string* err) {
out->image_ = isa::ImageView::from(image);
if (!out->image_.ok()) {
if (err) {
*err = out->image_.error();
}
return false;
}
// 数据区工作副本(8 字节定宽槽)
out->data_.assign(out->image_.data_bytes(),
out->image_.data_bytes() + out->image_.data_len());
out->ctu_prev_.assign(out->data_len() / 8, 0);
// MAIN 帧(跨周期保留)
const uint32_t entry = out->image_.header().entry_fn_id;
const isa::FuncRow main = out->image_.func_row(entry);
out->frames_.clear();
Frame f;
f.fn_id = entry;
f.regs.assign(main.nregs, 0);
out->frames_.push_back(std::move(f));
out->pc_ = 0;
out->cycle_count_ = 0;
out->fault_ = Fault::None;
out->ended_ = false;
return true;
} }
Fault Machine::run_cycle() {
cycle_count_ = 0;
ended_ = false;
fault_ = Fault::None;
pc_ = 0;
while (!ended_ && fault_ == Fault::None) {
if (!exec_one()) {
break;
}
}
// 周期结束:清调用栈(保留 MAIN 帧,PROGRAM 变量状态跨周期)
while (frames_.size() > 1) {
frames_.pop_back();
}
return fault_;
}
bool Machine::step() {
if (ended_ || fault_ != Fault::None) {
return false;
}
return exec_one();
}
Fault Machine::fault() const { return fault_; }
uint32_t Machine::pc() const { return pc_; }
uint32_t Machine::fn_id() const { return cur_frame().fn_id; }
uint32_t Machine::cycle_count() const { return cycle_count_; }
uint32_t Machine::call_depth() const { return static_cast<uint32_t>(frames_.size()); }
uint8_t* Machine::data() { return data_.data(); }
const uint8_t* Machine::data() const { return data_.data(); }
size_t Machine::data_len() const { return data_.size(); }
int64_t Machine::reg(uint8_t i) const { return cur_frame().regs[i]; }
Machine::Frame& Machine::cur_frame() { return frames_.back(); }
const Machine::Frame& Machine::cur_frame() const { return frames_.back(); }
bool Machine::exec_one() {
// ---- 取指 ----
const isa::FuncRow row = image_.func_row(cur_frame().fn_id);
if (pc_ >= row.code_len) {
fault_ = Fault::BadOp;
return false;
}
const uint8_t* base = image_.code_bytes() + row.code_offset;
const isa::Instr w = reinterpret_cast<const uint32_t*>(base)[pc_];
// ---- 周期指令计数 ----
++cycle_count_;
if (cycle_count_ > image_.header().cycle_limit) {
fault_ = Fault::CycleLimit;
return false;
}
// 先指向下一条(跳转指令再按"相对下一条"语义叠加偏移)
++pc_;
const isa::Op op = isa::op(w);
const uint8_t rd = isa::rd(w);
const uint8_t ra = isa::a(w);
const uint8_t rb = isa::b(w);
std::vector<int64_t>& regs = cur_frame().regs;
// 寄存器越界检查(仅对把字段当寄存器的指令;a|b 作偏移/槽号/常量 id 时跳过)
const bool uses_rd = op == isa::Op::MOVE || op == isa::Op::NOT || op == isa::Op::AND ||
op == isa::Op::OR || op == isa::Op::ADD || op == isa::Op::SUB ||
op == isa::Op::MUL || op == isa::Op::DIV || op == isa::Op::CMP_EQ ||
op == isa::Op::CMP_NE || op == isa::Op::CMP_LT ||
op == isa::Op::CMP_LE || op == isa::Op::CMP_GT ||
op == isa::Op::CMP_GE || op == isa::Op::JT || op == isa::Op::JF ||
op == isa::Op::LOADK || op == isa::Op::LOAD_I ||
op == isa::Op::LOAD_M || op == isa::Op::LOAD_GLOBAL ||
op == isa::Op::STORE_Q || op == isa::Op::STORE_M ||
op == isa::Op::STORE_GLOBAL;
const bool uses_ra = op == isa::Op::MOVE || op == isa::Op::NOT || op == isa::Op::AND ||
op == isa::Op::OR || op == isa::Op::ADD || op == isa::Op::SUB ||
op == isa::Op::MUL || op == isa::Op::DIV || op == isa::Op::CMP_EQ ||
op == isa::Op::CMP_NE || op == isa::Op::CMP_LT ||
op == isa::Op::CMP_LE || op == isa::Op::CMP_GT ||
op == isa::Op::CMP_GE;
const bool uses_rb = uses_ra;
if ((uses_rd && rd >= regs.size()) || (uses_ra && ra >= regs.size()) ||
(uses_rb && rb >= regs.size())) {
fault_ = Fault::BadOp;
return false;
}
switch (op) {
case isa::Op::MOVE:
regs[rd] = regs[ra];
return true;
case isa::Op::LOADK: {
const uint16_t cid = isa::imm16(w);
if (cid >= image_.header().n_consts) {
fault_ = Fault::BadConst;
return false;
}
const isa::ConstEntry c = image_.const_entry(cid);
if (c.tag == isa::types::Bool) {
regs[rd] = c.value ? 1 : 0;
} else if (c.tag == isa::types::Int) {
regs[rd] = static_cast<int16_t>(c.value); // 符号扩展
} else {
regs[rd] = static_cast<int64_t>(c.value);
}
return true;
}
case isa::Op::NOT:
regs[rd] = (regs[ra] == 0) ? 1 : 0;
return true;
case isa::Op::AND:
regs[rd] = (regs[ra] != 0 && regs[rb] != 0) ? 1 : 0;
return true;
case isa::Op::OR:
regs[rd] = (regs[ra] != 0 || regs[rb] != 0) ? 1 : 0;
return true;
case isa::Op::ADD:
regs[rd] = isa::types::sat_add(static_cast<int16_t>(regs[ra]),
static_cast<int16_t>(regs[rb]));
return true;
case isa::Op::SUB:
regs[rd] = isa::types::sat_sub(static_cast<int16_t>(regs[ra]),
static_cast<int16_t>(regs[rb]));
return true;
case isa::Op::MUL:
regs[rd] = isa::types::sat_mul(static_cast<int16_t>(regs[ra]),
static_cast<int16_t>(regs[rb]));
return true;
case isa::Op::DIV:
regs[rd] = isa::types::sat_div(static_cast<int16_t>(regs[ra]),
static_cast<int16_t>(regs[rb]));
return true;
case isa::Op::CMP_EQ:
case isa::Op::CMP_NE:
case isa::Op::CMP_LT:
case isa::Op::CMP_LE:
case isa::Op::CMP_GT:
case isa::Op::CMP_GE: {
const int64_t l = regs[ra];
const int64_t r = regs[rb];
bool res = false;
switch (op) {
case isa::Op::CMP_EQ: res = (l == r); break;
case isa::Op::CMP_NE: res = (l != r); break;
case isa::Op::CMP_LT: res = (l < r); break;
case isa::Op::CMP_LE: res = (l <= r); break;
case isa::Op::CMP_GT: res = (l > r); break;
case isa::Op::CMP_GE: res = (l >= r); break;
default: break;
}
regs[rd] = res ? 1 : 0;
return true;
}
case isa::Op::JMP:
pc_ += isa::off16(w);
return true;
case isa::Op::JT:
if (regs[rd] != 0) {
pc_ += isa::off16(w);
}
return true;
case isa::Op::JF:
if (regs[rd] == 0) {
pc_ += isa::off16(w);
}
return true;
case isa::Op::LOAD_I:
case isa::Op::LOAD_M:
case isa::Op::LOAD_GLOBAL: {
const uint16_t slot = isa::imm16(w);
if (!slot_get(slot, &regs[rd])) {
fault_ = Fault::BadSlot;
return false;
}
return true;
}
case isa::Op::STORE_Q:
case isa::Op::STORE_M:
case isa::Op::STORE_GLOBAL: {
const uint16_t slot = isa::imm16(w);
if (!slot_set(slot, regs[rd])) {
fault_ = Fault::BadSlot;
return false;
}
return true;
}
case isa::Op::CALL:
fault_ = do_call(isa::imm16(w));
return fault_ == Fault::None;
case isa::Op::RET:
fault_ = do_ret();
if (fault_ != Fault::None) {
return false;
}
return !ended_; // MAIN 的 RET → 周期结束(返回 false)
case isa::Op::CAL_TON:
case isa::Op::CAL_TOF:
case isa::Op::CAL_CTU:
fault_ = do_cal(static_cast<uint8_t>(op), isa::imm16(w));
return fault_ == Fault::None;
default:
fault_ = Fault::BadOp;
return false;
}
}
bool Machine::slot_get(uint16_t slot, int64_t* out) const {
const size_t off = static_cast<size_t>(slot) * 8;
if (off + 8 > data_.size()) {
return false;
}
int64_t v = 0;
for (int i = 0; i < 8; ++i) {
v |= static_cast<int64_t>(data_[off + i]) << (8 * i);
}
*out = v;
return true;
}
bool Machine::slot_set(uint16_t slot, int64_t v) {
const size_t off = static_cast<size_t>(slot) * 8;
if (off + 8 > data_.size()) {
return false;
}
for (int i = 0; i < 8; ++i) {
data_[off + i] = static_cast<uint8_t>((v >> (8 * i)) & 0xFFu);
}
return true;
}
Fault Machine::do_call(uint32_t fn_id) {
if (fn_id >= image_.header().n_funcs) {
return Fault::BadOp;
}
if (frames_.size() >= 64) {
return Fault::StackOverflow;
}
const isa::FuncRow row = image_.func_row(fn_id);
Frame f;
f.fn_id = fn_id;
f.regs.assign(row.nregs, 0);
f.ret_pc = pc_; // 已指向 CALL 的下一条
f.ret_fn_id = cur_frame().fn_id;
// 调用约定:复制当前帧 r0..r7 → 新帧(实参已由调用点 MOVE 进 r1..r7
const size_t n = std::min<size_t>(8, std::min(cur_frame().regs.size(), f.regs.size()));
for (size_t i = 0; i < n; ++i) {
f.regs[i] = cur_frame().regs[i];
}
frames_.push_back(std::move(f));
pc_ = 0;
return Fault::None;
}
Fault Machine::do_ret() {
if (frames_.size() <= 1) {
ended_ = true; // MAIN 的 RET:周期结束
return Fault::None;
}
Frame& cur = cur_frame();
Frame& caller = frames_[frames_.size() - 2];
// 调用约定:复制当前帧 r0..r7 → 调用方(r0 = 结果,r1..r7 原样返回)
const size_t n = std::min<size_t>(8, std::min(cur.regs.size(), caller.regs.size()));
for (size_t i = 0; i < n; ++i) {
caller.regs[i] = cur.regs[i];
}
pc_ = cur.ret_pc;
frames_.pop_back();
return Fault::None;
}
Fault Machine::do_cal(uint8_t op, uint16_t slot) {
// 内建 FB 布局冻结(Doc/compiler/符号表与链接.md):
// TON/TOFin/pt/q/et(字段 0/1/2/3
// CTUcu/r/pv/q/cv(字段 0/1/2/3/4
// 实例槽号从 slot(基槽)起;字段偏移 = 字段序号 × 8(8 字节定宽)
const int64_t dt = image_.header().dt_ms;
int64_t v = 0;
if (op == static_cast<uint8_t>(isa::Op::CAL_TON) ||
op == static_cast<uint8_t>(isa::Op::CAL_TOF)) {
// 字段:in=0 pt=1 q=2 et=3
if (!slot_get(static_cast<uint16_t>(slot + 0), &v)) return Fault::BadSlot;
const bool in = v != 0;
if (!slot_get(static_cast<uint16_t>(slot + 1), &v)) return Fault::BadSlot;
const int64_t pt = v;
int64_t et = 0;
if (!slot_get(static_cast<uint16_t>(slot + 3), &et)) return Fault::BadSlot;
int64_t q = 0;
if (op == static_cast<uint8_t>(isa::Op::CAL_TON)) {
// TONin 真 → et += dt(到 pt 停)、q = et ≥ ptin 假 → et = 0、q = 0
if (in) {
et += dt;
if (pt > 0 && et >= pt) {
et = pt;
}
q = (et >= pt) ? 1 : 0;
} else {
et = 0;
q = 0;
}
} else {
// TOFin 真 → q = 1、et = 0;掉电 → et += dt、et ≥ pt → q = 0
if (in) {
et = 0;
q = 1;
} else {
et += dt;
q = (pt > 0 && et >= pt) ? 0 : 1;
}
}
if (!slot_set(static_cast<uint16_t>(slot + 3), et)) return Fault::BadSlot;
if (!slot_set(static_cast<uint16_t>(slot + 2), q)) return Fault::BadSlot;
return Fault::None;
}
if (op == static_cast<uint8_t>(isa::Op::CAL_CTU)) {
// 字段:cu=0 r=1 pv=2 q=3 cv=4
if (slot >= ctu_prev_.size()) return Fault::BadSlot;
if (!slot_get(static_cast<uint16_t>(slot + 0), &v)) return Fault::BadSlot;
const bool cu = v != 0;
if (!slot_get(static_cast<uint16_t>(slot + 1), &v)) return Fault::BadSlot;
const bool r = v != 0;
if (!slot_get(static_cast<uint16_t>(slot + 2), &v)) return Fault::BadSlot;
const int64_t pv = v;
int64_t cv = 0;
if (!slot_get(static_cast<uint16_t>(slot + 4), &cv)) return Fault::BadSlot;
// 上升沿:本周期 cu 真且上次 cu 假 → cv + 1;r → 复位
const bool prev = ctu_prev_[slot] != 0;
if (r) {
cv = 0;
} else if (cu && !prev) {
cv += 1;
}
ctu_prev_[slot] = cu ? 1 : 0; // 记录本次 cu(跨周期)
if (!slot_set(static_cast<uint16_t>(slot + 4), cv)) return Fault::BadSlot;
if (!slot_set(static_cast<uint16_t>(slot + 3), (cv >= pv) ? 1 : 0)) {
return Fault::BadSlot;
}
return Fault::None;
}
return Fault::BadOp;
}
} // namespace vm