Compare commits
7
Commits
3c42a72e86
...
8c0fc4fe98
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c0fc4fe98 | ||
|
|
ada9e5f99e | ||
|
|
a84a1a62ce | ||
|
|
efe4dd1a2c | ||
|
|
815eb87a46 | ||
|
|
ce948451e0 | ||
|
|
cd417cd27b |
@@ -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 = 槽号**(u16,0..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
@@ -1,6 +1,6 @@
|
|||||||
# .stb 映像文件结构
|
# .stb 映像文件结构
|
||||||
|
|
||||||
编译产物 `.stb` 的**面向使用者**的结构说明。规范以 [`指令与映像.md`](指令与映像.md) 为准,本文用 `line1.stb`(184 字节)做真实拆解。
|
编译产物 `.stb` 的**面向使用者**的结构说明。规范以 [`指令与映像.md`](指令与映像.md) 为准,本文用 `line1.stb`(224 字节)做真实拆解。
|
||||||
|
|
||||||
## 概览
|
## 概览
|
||||||
|
|
||||||
@@ -74,15 +74,15 @@ line1(2 行):
|
|||||||
- `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_0(io.input 绑定 → LOAD_I) |
|
| 0x64 | `12 08 01 00` | `LOAD_I r8, 1` | I0_0(io.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_0(BOOL,初值 0) |
|
| 0 | 0xA8 | EmergencyStop(BOOL,初值 0) |
|
||||||
| 0xAC..0xAF | 填充(实例块 8 对齐) |
|
| 1 | 0xB0 | I0_0(BOOL) |
|
||||||
| 0xB0..0xB2 | starter 实例:start、stop、q(BOOL) |
|
| 2 | 0xB8 | I0_1(BOOL) |
|
||||||
| 0xB3..0xB7 | 填充 |
|
| 3 | 0xC0 | Q0_0(BOOL) |
|
||||||
|
| 4 | 0xC8 | starter.start(BOOL) |
|
||||||
|
| 5 | 0xD0 | starter.stop(BOOL) |
|
||||||
|
| 6 | 0xD8 | starter.q(BOOL) |
|
||||||
|
|
||||||
## 用工具验证
|
## 用工具验证
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -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 64:basis `0xcbf29ce484222325`,prime `0x100000001b3`。
|
工程哈希:收录源文件路径排序后对内容做 **FNV-1a 64**,只保证回放槽位一致,不是密码学哈希。FNV-1a 64:basis `0xcbf29ce484222325`,prime `0x100000001b3`。
|
||||||
|
|
||||||
|
|||||||
@@ -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 → 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`(`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)
|
||||||
|
|
||||||
|
### 步骤 3:CALL / 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` 推进(每周期一次调用 = 推进一次),不读系统时钟
|
||||||
|
- 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`(`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` 故障
|
||||||
|
- TON:dt=10、pt=30 → 第 3 周期 q=1;CTU:上升沿计数
|
||||||
|
- **确定性**:同一 I 序列跑两遍,数据区完全一致
|
||||||
|
|
||||||
|
### 步骤 7:验证 + 提交
|
||||||
|
|
||||||
|
- `cmake --build` + `ctest`(新增 `vm_cycles` 后 10/10 全绿);20 用例扫描:14 正例执行通过
|
||||||
|
|||||||
+38
-83
@@ -7,15 +7,14 @@
|
|||||||
* @details 设计说明(详见 Doc/compiler/寄存器码.md):
|
* @details 设计说明(详见 Doc/compiler/寄存器码.md):
|
||||||
* - 切片 1~6:帧/字面量/MOVE/RET;全局数据区;短路 AND/OR;CMP 与 IF;WHILE 与四则;
|
* - 切片 1~6:帧/字面量/MOVE/RET;全局数据区;短路 AND/OR;CMP 与 IF;WHILE 与四则;
|
||||||
* 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 成功;false(err 已写)
|
* @return true 成功;false(err 已写)
|
||||||
*/
|
*/
|
||||||
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 体字段上下文(可空)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -77,6 +77,8 @@ namespace isa {
|
|||||||
// 只读视图:校验过的映像
|
// 只读视图:校验过的映像
|
||||||
class ImageView {
|
class ImageView {
|
||||||
public:
|
public:
|
||||||
|
// 默认构造为无效态(ok() == false,error() == "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_;
|
||||||
|
|||||||
@@ -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
@@ -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 := I(INT 偏移 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); // 实例 3B(start@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); // pt(TIME 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..31(in@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.CV(INT 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_0(io.input)
|
CHECK(std::strcmp(buf, "LOAD_I r8, 1") == 0); // I0_0(io.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_0(io.output)
|
CHECK(std::strcmp(buf, "STORE_Q r8, 3") == 0); // Q0_0(io.output)
|
||||||
isa::disasm(im[16], buf, sizeof buf);
|
isa::disasm(im[16], buf, sizeof buf);
|
||||||
|
|||||||
@@ -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 := 5;r9 := 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←r8;CALL 1;结果 r0 → r8
|
||||||
|
// fn1:r0 = r1 + r2(r1/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 IF:sel=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 WHILE:n=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 + EXTERNAL:x = 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 FUNCTION:x = 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 TON:dt=10、pt=30,in 恒真 → 第 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 line1:I 组合 → 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. 用例 06:cycle_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;
|
||||||
|
}
|
||||||
@@ -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 → RET;MAIN 帧跨周期保留,调用栈深度上限 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 成功;false(映像非法,err 已写)
|
||||||
|
*/
|
||||||
|
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/CTU(12.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
@@ -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/JF)、LOAD/STORE(slot × 8);RET 仅处理 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, ®s[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/TOF:in/pt/q/et(字段 0/1/2/3)
|
||||||
|
// CTU:cu/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)) {
|
||||||
|
// TON:in 真 → et += dt(到 pt 停)、q = et ≥ pt;in 假 → 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 {
|
||||||
|
// TOF:in 真 → 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
|
||||||
|
|||||||
Reference in New Issue
Block a user