扩充内置功能块为 8 个:TON/TOF/TP/CTU/CTD/CTUD/R_TRIG/F_TRIG。

- isa:CAL_* 操作码连续占 24..31(CALL/RET 后移为 32/33,kOpCount=34);disasm 支持 5 个新操作码
- 词法/语法/链接:新 8 个关键字与冻结布局(TP=in/pt/q/et、CTD=cd/ld/pv/q/cv、
  CTUD=cu/cd/r/lu/pv/qu/qd/cv、R_TRIG/F_TRIG=clk/q)
- VM:do_cal 全 8 个语义(TP 脉冲、CTD 递减、CTUD 双向+装载、R_TRIG 上升沿、F_TRIG 下降沿);
  edge_prev_ 每槽 2 字节存边沿上次输入;exec_one 分发补 5 个新操作码
- 验证:TP 30ms 脉冲 2 周期、CTD 装载+递减到 q=1、CTUD cu/cd 双向、R/F_TRIG 交替;
  ctest 11/11 全绿
- 文档:指令与映像/词法/初步计划/符号表与链接/指令执行/扫描周期/使用说明 同步
This commit is contained in:
2026-08-21 20:38:27 +08:00
parent 66d9c6c548
commit 26256a36b9
18 changed files with 249 additions and 64 deletions
+2 -2
View File
@@ -80,7 +80,7 @@ compiled: line1.stb (184 bytes, 2 functions, 4 globals)
| `project.name` | 是 | 工程名 |
| `project.entry` | 是 | 第一版必须 `"program MAIN"` |
| `project.cycle_limit` | 是 | 每周期指令上限(>0) |
| `project.dt_ms` | 是 | 本周期 Δt 毫秒(>0),给 TON/TOF/CTU |
| `project.dt_ms` | 是 | 本周期 Δt 毫秒(>0),给内置 FB 定时器 |
| `files.st` | 是 | 源文件列表(非空数组) |
| `gvl.file` | 否 | 唯一允许 `VAR_GLOBAL` 的文件;已在 `files.st` 则不重复收录 |
| `[[io.input]]` / `[[io.output]]` | 否 | `var` + `channel` + `bit``var` 必须已在 GVL 声明 |
@@ -110,7 +110,7 @@ bit = 2
**变量段**`VAR` / `VAR_INPUT` / `VAR_OUTPUT` / `VAR_GLOBAL`(仅 `gvl.file` 顶层)/ `VAR_EXTERNAL`
**类型**`BOOL` / `INT` / `TIME`;内建 FB 类型 `TON` / `TOF` / `CTU`(关键字,不可作变量名)。
**类型**`BOOL` / `INT` / `TIME`;内建 FB 类型 `TON` / `TOF` / `TP` / `CTU` / `CTD` / `CTUD` / `R_TRIG` / `F_TRIG`(关键字,不可作变量名)。
**语句**:赋值 `:=``IF / ELSIF / ELSE / END_IF``WHILE / END_WHILE`、FB 调用 `fb(in := ..., ...);`、字段读 `fb.Q`
+8 -3
View File
@@ -12,7 +12,7 @@ compiler 模块的符号收集与链接。输入:`Project`toml+ 各 `.st
- `FUNCTION` 标无状态;`FUNCTION_BLOCK` 必须先有实例才能调用
- `CALL` 目标解析成 `fn_id`(立即数),无函数指针
- 校验:同名全局、未声明 `io.var`、重复 POU 名、未声明 FB 实例、函数循环调用 → 失败
- FB 实例布局:按类型算字段清单(输入/输出/内部/内建 TON/TOF/CTU 固定字段
- FB 实例布局:按类型算字段清单(输入/输出/内部/内建 8 个 FB 固定字段(12.11 扩充)
**不做(第一版)**
@@ -70,15 +70,20 @@ struct Symbol {
## FB 实例布局
- 用户 FB:字段 = 全部 `VAR_INPUT` + `VAR_OUTPUT` + `VAR`(内部),按声明段序排
- 内建(冻结,isa 规格):
- 内建(冻结,isa 规格12.11 扩为 8 个):
| FB | 字段(名 → 类型) |
|---|---|
| TON | in BOOL, pt TIME, q BOOL, et TIME |
| TOF | in BOOL, pt TIME, q BOOL, et TIME |
| TP | in BOOL, pt TIME, q BOOL, et TIME |
| CTU | cu BOOL, r BOOL, pv INT, q BOOL, cv INT |
| CTD | cd BOOL, ld BOOL, pv INT, q BOOL, cv INT |
| CTUD | cu BOOL, cd BOOL, r BOOL, lu BOOL, pv INT, qu BOOL, qd BOOL, cv INT |
| R_TRIG | clk BOOL, q BOOL |
| F_TRIG | clk BOOL, q BOOL |
- 实例布局 = 类型字段表的一份拷贝;字段偏移 12.8 按对齐规则算
- 实例布局 = 类型字段表的一份拷贝;字段偏移 = 字段序号 × 88 字节定宽槽)
## 错误
+2 -1
View File
@@ -26,11 +26,12 @@ BOOL INT TIME
IF ELSIF ELSE END_IF WHILE END_WHILE
AND OR NOT
TRUE FALSE
TON TOF CTU
TON TOF TP CTU CTD CTUD R_TRIG F_TRIG
```
> 12.5 修订:`THEN`、`DO` 补入关键字表(`IF ... THEN` / `WHILE ... DO` 语法需要),
> 与 12.1 原表合并,其余不变。
> 12.11 修订:内置 FB 扩为 8 个(TON/TOF/TP/CTU/CTD/CTUD/R_TRIG/F_TRIG)。
其余单词一律当标识符;`VAR_IN_OUT``REF``CLASS` 等由语法层(12.5)出明确错误。
+1 -1
View File
@@ -86,7 +86,7 @@ line1 `MAIN` 前 5 条(`0x60` 起,slot 为 8 字节定宽槽号):
## FB 布局段(`offset_fb` 起)
v1 为空(用户 FB 内联展开,字段偏移已算进数据段;内建 TON/TOF/CTU 布局冻结在编译器中)。行格式约定:`[field_count:u32][field_count × (tag:u32, offset:u32)]`offset 相对实例基址。
v1 为空(用户 FB 内联展开,字段偏移已算进数据段;内建 8 个 FB 布局冻结在编译器中(12.11 扩充))。行格式约定:`[field_count:u32][field_count × (tag:u32, offset:u32)]`offset 相对实例基址。
## 数据段(`offset_data` 起)
+5 -1
View File
@@ -106,11 +106,15 @@ JMP offset
JT/JF r, offset
LOAD_I / STORE_Q / LOAD_M / STORE_M
LOAD_GLOBAL / STORE_GLOBAL
CAL_TON / CAL_TOF / CAL_CTU
CAL_TON / CAL_TOF / CAL_TP / CAL_CTU / CAL_CTD / CAL_CTUD / CAL_R_TRIG / CAL_F_TRIG
CALL fn_id
RET
```
> 12.11 修订:内置功能块扩为 8 个(TON/TOF/TP/CTU/CTD/CTUD/R_TRIG/F_TRIG),
> `CAL_*` 操作码**连续占 24..31**(原 CALL=27、RET=28 后移为 32、33)。
> 操作码数值即编码,本表为准。
局部 / `VAR` = 固定寄存器;表达式临时值往后编号;`nregs` 写进函数头。
`I`/`Q`/`M` 走映像指令,不是通用寄存器。
FB 实例固定布局,字段偏移编译期算死。
+2 -2
View File
@@ -10,7 +10,7 @@ vm 模块:寄存器虚拟机。CMake 目标:`vm``STATIC`),只依赖 [
1. 按 sidecar 的 io 绑定,采样 → I / 输入全局
2. 从 PROGRAM MAIN 的 pc=0 执行到 RET
3. 写回 Q / 输出全局
4. 用本周期 Δt 推进 TON / TOF / CTU(不用 wall clock
4. 用本周期 Δt 推进内置 FB(不用 wall clock
```
## 边界
@@ -97,7 +97,7 @@ namespace vm {
- `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
### 步骤 4:定时器与边沿(8 个 CAL_*
- 执行时用映像 `dt_ms` 推进(每周期一次调用 = 推进一次),不读系统时钟
- TONin 真 → et += dt(到 pt 停)、q = et ≥ ptin 假 → et = 0
+14 -10
View File
@@ -80,7 +80,7 @@ STORE_* slot, rs → 写 data[slot*8 .. slot*8+8) = rs8 字节原样)
| `JT rd, off` / `JF rd, off` | `reg[rd] != 0`JT/ `== 0`JF)时 `pc += off`,否则正常 +1 |
| `LOAD_* rd, slot` | 见数据区访问 |
| `STORE_* slot, rs` | 见数据区访问 |
| `CAL_TON/TOF/CTU slot` | 见定时器推进 |
| `CAL_TON/TOF/TP/CTU/CTD/CTUD/R_TRIG/F_TRIG slot` | 见定时器与边沿推进 |
| `CALL fn_id` / `RET` | 见帧与调用栈 |
### 跳转语义
@@ -95,26 +95,30 @@ STORE_* slot, rs → 写 data[slot*8 .. slot*8+8) = rs8 字节原样)
- 每条指令执行后 `cycle_count++`;超过映像头 `cycle_limit``CycleLimit` 故障(周期中止)
- `JMP` / `JT` / `JF` / `CALL` 只算 1 条(cycle_limit 以"执行的指令数"计)
## 定时器推进(CAL_TON / CAL_TOF / CAL_CTU
## 定时器与边沿推进(CAL_TON / CAL_TOF / CAL_TP / CAL_CTU / CAL_CTD / CAL_CTUD / CAL_R_TRIG / CAL_F_TRIG
`CAL_* slot``slot` 是实例**基槽号**;字段偏移 = 实例布局字段序号 × 8内建布局冻结:TON/TOF = in/pt/q/etCTU = cu/r/pv/q/cv)。每次执行 = 推进一次,Δt = 映像头 `dt_ms`(不读系统时钟)
`CAL_* slot``slot` 是实例**基槽号**;字段偏移 = 字段序号 × 8(布局冻结:TON/TOF/TP = in/pt/q/etCTU = cu/r/pv/q/cvCTD = cd/ld/pv/q/cvCTUD = cu/cd/r/lu/pv/qu/qd/cvR_TRIG/F_TRIG = clk/q)。每次执行 = 推进一次,Δt = 映像头 `dt_ms`(不读系统时钟)。边沿检测的"上次输入"存 VM 侧 `edge_prev_`(每实例基槽 2 字节),跨周期保留
```text
CAL_TON
in = 槽[in] 非零;pt = 槽[pt]et = 槽[et]q = 槽[q]
in 为真:et += dtet ≥ pt 时停在 pt);q = (et ≥ pt) ? 1 : 0
in 为假:et = 0q = 0
CAL_TOF
in 为真:q = 1et = 0
in 掉电:et += dtet ≥ pt 时 q = 0
(in 再次为真 → 重新 q = 1, et = 0
CAL_CTU
上升沿检测:本周期 cu 为真且上次 cu 为假 → cv += 1
(上次 cu 存 VM 侧 vector,按实例基槽索引,跨周期保留)
r 为真 → cv = 0
q = (cv ≥ pv) ? 1 : 0
CAL_TP(脉冲)
in 上升沿启动 PT 时长脉冲(期间 in 变化不影响)
计时中:et += dtet ≥ pt → q = 0、et 归零(脉冲结束);否则 q = 1
非计时:et = 0q = 0
CAL_CTUcu 上升沿 → cv += 1r → cv = 0q = (cv ≥ pv) ? 1 : 0
CAL_CTDcd 上升沿 → cv -= 1ld → cv = pvq = (cv ≤ 0) ? 1 : 0
CAL_CTUDcu 上升沿 → cv += 1cd 上升沿 → cv -= 1r → cv = 0lu → cv = pv
qu = (cv ≥ pv) ? 1 : 0qd = (cv ≤ 0) ? 1 : 0
CAL_R_TRIGq = clk 且上次 clk 假(上升沿)
CAL_F_TRIGq = !clk 且上次 clk 真(下降沿)
```
## 故障
+6 -6
View File
@@ -45,7 +45,7 @@
1. 按 toml 的 io 绑定,采样 → I / 输入全局
2. 从 PROGRAM MAIN 的 pc=0 执行到 RET
3. 写回 Q / 输出全局
4. 用本周期 Δt 推进 TON / TOF / CTU不用 wall clock
4. 用本周期 Δt 推进内置 FBTON/TOF/TP/CTU/CTD/CTUD/R_TRIG/F_TRIG不用 wall clock
```
运行时是 **一台 VM、一个入口、一份映像**。多文件只影响源码怎么切、怎么链接。
@@ -219,7 +219,7 @@ JMP offset
JT/JF r, offset
LOAD_I / STORE_Q / LOAD_M / STORE_M
LOAD_GLOBAL / STORE_GLOBAL
CAL_TON / CAL_TOF / CAL_CTU
CAL_TON / CAL_TOF / CAL_TP / CAL_CTU / CAL_CTD / CAL_CTUD / CAL_R_TRIG / CAL_F_TRIG
CALL fn_id
RET
```
@@ -354,7 +354,7 @@ CMake 三个库能空编译:`isa`、`compiler`、`vm`;两个可执行 `STCom
| `project.name` | 工程名 | 必填 |
| `project.entry` | 入口 | 第一版必须是 `program MAIN` |
| `project.cycle_limit` | 每周期指令上限 | 如 `100000`;超时是故障 |
| `project.dt_ms` | 本周期 Δt | 给 TON / TOF / CTU,不是 wall clock |
| `project.dt_ms` | 本周期 Δt | 给内置 FB 定时器,不是 wall clock |
| `files.st` | 源文件列表 | 编译前全部已知 |
| `gvl.file` | 唯一允许 `VAR_GLOBAL` 的文件 | 已在 `files.st` 则不重复收录 |
| `[[io.input]]` / `[[io.output]]` | `var` + `channel` + `bit` | 可选;`var` 必须已在 GVL |
@@ -370,7 +370,7 @@ CMake 三个库能空编译:`isa`、`compiler`、`vm`;两个可执行 `STCom
`THEN` `DO` 12.5 修订补入:`IF…THEN` / `WHILE…DO` 语法需要)
`AND` `OR` `NOT`
`TRUE` `FALSE`
`TON` `TOF` `CTU`
`TON` `TOF` `TP` `CTU` `CTD` `CTUD` `R_TRIG` `F_TRIG` (12.11 修订:内置 FB 扩为 8 个)
其余一律当标识符或直接拒绝(`VAR_IN_OUT``REF``CLASS` 等出明确错误)。
@@ -449,7 +449,7 @@ CMake 三个库能空编译:`isa`、`compiler`、`vm`;两个可执行 `STCom
1. POU 外壳:`PROGRAM` / `FUNCTION` / `FUNCTION_BLOCK` 与对应 `END_*`
2. 变量段:`VAR` / `VAR_INPUT` / `VAR_OUTPUT` / `VAR_GLOBAL` / `VAR_EXTERNAL`
3. 类型名只接受 `BOOL` `INT` `TIME`;内置 FB 名 `TON` `TOF` `CTU`
3. 类型名只接受 `BOOL` `INT` `TIME`;内置 FB 名 `TON` `TOF` `TP` `CTU` `CTD` `CTUD` `R_TRIG` `F_TRIG` (12.11 修订:内置 FB 扩为 8 个)
4. 语句:赋值、`IF``WHILE`、FB 调用 `fb(in := …)`、字段 `fb.Q`
5. 表达式:`NOT``AND`/`OR`(AST 里保留短路语义)、比较、加减乘除
@@ -512,7 +512,7 @@ CMake 三个库能空编译:`isa`、`compiler`、`vm`;两个可执行 `STCom
此时才写执行循环。只认 `isa` 映像。
```text
采样 I → pc=0 执行到 RET → 写回 Q → 用本周期 Δt 推进 TON/TOF/CTU
采样 I → pc=0 执行到 RET → 写回 Q → 用本周期 Δt 推进内置 FB
```
- 译码 + 大 `switch`;寄存器文件按函数头 `nregs` 分配。
+5
View File
@@ -46,7 +46,12 @@ namespace compiler {
FALSE,
TON,
TOF,
TP,
CTU,
CTD,
CTUD,
R_TRIG,
F_TRIG,
// 字面量
IDENT, // text = 小写名
INT_LIT, // int_value
+12 -4
View File
@@ -327,10 +327,18 @@ namespace {
f.code.push_back(isa::enc_slot(isa::Op::STORE_GLOBAL, t, it->second));
}
if (inst->type_name == "ton" || inst->type_name == "tof" ||
inst->type_name == "ctu") {
const isa::Op op = inst->type_name == "ton" ? isa::Op::CAL_TON
: inst->type_name == "tof" ? isa::Op::CAL_TOF
: isa::Op::CAL_CTU;
inst->type_name == "tp" || inst->type_name == "ctu" ||
inst->type_name == "ctd" || inst->type_name == "ctud" ||
inst->type_name == "r_trig" || inst->type_name == "f_trig") {
const isa::Op op =
inst->type_name == "ton" ? isa::Op::CAL_TON
: inst->type_name == "tof" ? isa::Op::CAL_TOF
: inst->type_name == "tp" ? isa::Op::CAL_TP
: inst->type_name == "ctu" ? isa::Op::CAL_CTU
: inst->type_name == "ctd" ? isa::Op::CAL_CTD
: inst->type_name == "ctud" ? isa::Op::CAL_CTUD
: inst->type_name == "r_trig" ? isa::Op::CAL_R_TRIG
: isa::Op::CAL_F_TRIG;
f.code.push_back(isa::enc_slot(op, 0, inst->base));
return true;
}
+6 -1
View File
@@ -78,10 +78,15 @@ namespace {
// 字面量
{"true", Tok::TRUE},
{"false", Tok::FALSE},
// 内 FB 类型名(v1 冻结三件套
// 内 FB 类型名(12.11 扩充为 8 个
{"ton", Tok::TON},
{"tof", Tok::TOF},
{"tp", Tok::TP},
{"ctu", Tok::CTU},
{"ctd", Tok::CTD},
{"ctud", Tok::CTUD},
{"r_trig", Tok::R_TRIG},
{"f_trig", Tok::F_TRIG},
};
/**
+18 -2
View File
@@ -96,24 +96,40 @@ namespace {
return k == TypeKind::Bool || k == TypeKind::Int || k == TypeKind::Time;
}
// 内建 FB 布局(冻结,见 Doc/compiler/符号表与链接.md
// 内建 FB 布局(冻结,见 Doc/compiler/符号表与链接.md12.11 扩为 8 个
const FbLayout kTonLayout{"ton", {{"in", TypeKind::Bool}, {"pt", TypeKind::Time},
{"q", TypeKind::Bool}, {"et", TypeKind::Time}}};
const FbLayout kTofLayout{"tof", {{"in", TypeKind::Bool}, {"pt", TypeKind::Time},
{"q", TypeKind::Bool}, {"et", TypeKind::Time}}};
const FbLayout kTpLayout{"tp", {{"in", TypeKind::Bool}, {"pt", TypeKind::Time},
{"q", TypeKind::Bool}, {"et", TypeKind::Time}}};
const FbLayout kCtuLayout{"ctu", {{"cu", TypeKind::Bool}, {"r", TypeKind::Bool},
{"pv", TypeKind::Int}, {"q", TypeKind::Bool},
{"cv", TypeKind::Int}}};
const FbLayout kCtdLayout{"ctd", {{"cd", TypeKind::Bool}, {"ld", TypeKind::Bool},
{"pv", TypeKind::Int}, {"q", TypeKind::Bool},
{"cv", TypeKind::Int}}};
const FbLayout kCtudLayout{"ctud", {{"cu", TypeKind::Bool}, {"cd", TypeKind::Bool},
{"r", TypeKind::Bool}, {"lu", TypeKind::Bool},
{"pv", TypeKind::Int}, {"qu", TypeKind::Bool},
{"qd", TypeKind::Bool}, {"cv", TypeKind::Int}}};
const FbLayout kRTrigLayout{"r_trig", {{"clk", TypeKind::Bool}, {"q", TypeKind::Bool}}};
const FbLayout kFTrigLayout{"f_trig", {{"clk", TypeKind::Bool}, {"q", TypeKind::Bool}}};
/**
* @brief FB
* @param name FB
* @return ton / tof / ctu nullptr
* @return ton/tof/tp/ctu/ctd/ctud/r_trig/f_trig nullptr
*/
const FbLayout* builtin_layout(const std::string& name) {
if (name == "ton") return &kTonLayout;
if (name == "tof") return &kTofLayout;
if (name == "tp") return &kTpLayout;
if (name == "ctu") return &kCtuLayout;
if (name == "ctd") return &kCtdLayout;
if (name == "ctud") return &kCtudLayout;
if (name == "r_trig") return &kRTrigLayout;
if (name == "f_trig") return &kFTrigLayout;
return nullptr;
}
+25
View File
@@ -458,11 +458,36 @@ namespace {
t->name = "tof";
advance();
return true;
case Tok::TP:
t->kind = TypeKind::FbBuiltin;
t->name = "tp";
advance();
return true;
case Tok::CTU:
t->kind = TypeKind::FbBuiltin;
t->name = "ctu";
advance();
return true;
case Tok::CTD:
t->kind = TypeKind::FbBuiltin;
t->name = "ctd";
advance();
return true;
case Tok::CTUD:
t->kind = TypeKind::FbBuiltin;
t->name = "ctud";
advance();
return true;
case Tok::R_TRIG:
t->kind = TypeKind::FbBuiltin;
t->name = "r_trig";
advance();
return true;
case Tok::F_TRIG:
t->kind = TypeKind::FbBuiltin;
t->name = "f_trig";
advance();
return true;
case Tok::IDENT: {
const std::string word = cur().text;
if (is_forbidden(word)) {
+12 -5
View File
@@ -37,15 +37,21 @@ namespace isa {
STORE_M = 21, // STORE_M rs, slot
LOAD_GLOBAL = 22, // LOAD_GLOBAL rd, slot
STORE_GLOBAL = 23, // STORE_GLOBAL rs, slot
// 内置功能块(8 个连续,12.11 扩充:TON/TOF/TP/CTU/CTD/CTUD/R_TRIG/F_TRIG
CAL_TON = 24, // CAL_TON instance_slot
CAL_TOF = 25, // CAL_TOF instance_slot
CAL_CTU = 26, // CAL_CTU instance_slot
CALL = 27, // CALL fn_id
RET = 28, // RET
CAL_TP = 26, // CAL_TP instance_slot
CAL_CTU = 27, // CAL_CTU instance_slot
CAL_CTD = 28, // CAL_CTD instance_slot
CAL_CTUD = 29, // CAL_CTUD instance_slot
CAL_R_TRIG = 30, // CAL_R_TRIG instance_slot
CAL_F_TRIG = 31, // CAL_F_TRIG instance_slot
CALL = 32, // CALL fn_id
RET = 33, // RET
};
// 操作码总数
static const int kOpCount = 29;
static const int kOpCount = 34;
// 助记符:下标与 Op 数值一一对应
inline const char* mnemonic(Op op) {
@@ -56,7 +62,8 @@ namespace isa {
"JMP", "JT", "JF",
"LOAD_I", "STORE_Q", "LOAD_M", "STORE_M",
"LOAD_GLOBAL", "STORE_GLOBAL",
"CAL_TON", "CAL_TOF", "CAL_CTU",
"CAL_TON", "CAL_TOF", "CAL_TP", "CAL_CTU", "CAL_CTD", "CAL_CTUD",
"CAL_R_TRIG", "CAL_F_TRIG",
"CALL", "RET",
};
const int idx = static_cast<int>(op);
+5
View File
@@ -83,7 +83,12 @@ void disasm(Instr w, char* out, size_t cap) {
break;
case Op::CAL_TON:
case Op::CAL_TOF:
case Op::CAL_TP:
case Op::CAL_CTU:
case Op::CAL_CTD:
case Op::CAL_CTUD:
case Op::CAL_R_TRIG:
case Op::CAL_F_TRIG:
case Op::CALL:
snprintf(out, cap, "%s %u", m, static_cast<unsigned>(imm16(w)));
break;
+7 -3
View File
@@ -54,9 +54,13 @@ static bool test_sat() {
static bool test_op() {
using namespace isa;
CHECK(static_cast<int>(Op::MOVE) == 0);
CHECK(static_cast<int>(Op::RET) == 28);
CHECK(kOpCount == 29);
CHECK(std::strcmp(mnemonic(Op::CAL_CTU), "CAL_CTU") == 0);
CHECK(static_cast<int>(Op::CAL_TON) == 24);
CHECK(static_cast<int>(Op::CAL_F_TRIG) == 31); // 8 个 CAL_* 连续 24..31
CHECK(static_cast<int>(Op::CALL) == 32);
CHECK(static_cast<int>(Op::RET) == 33);
CHECK(kOpCount == 34);
CHECK(std::strcmp(mnemonic(Op::CAL_CTUD), "CAL_CTUD") == 0);
CHECK(std::strcmp(mnemonic(Op::CAL_F_TRIG), "CAL_F_TRIG") == 0);
CHECK(std::strcmp(mnemonic(static_cast<Op>(255)), "???") == 0);
return true;
}
+3 -1
View File
@@ -108,7 +108,9 @@ namespace vm {
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)
// 边沿检测上次输入(每槽 2 字节:[slot*2] 第一边沿、[slot*2+1] 第二边沿;
// CTU/CTD/R_TRIG/F_TRIG 用第一、CTUD 用两个),跨周期保留
std::vector<uint8_t> edge_prev_;
uint32_t pc_ = 0; // 当前 pc(相对本帧函数字节码段)
uint32_t cycle_count_ = 0; // 本周期指令数
Fault fault_ = Fault::None; // 最近一次故障
+116 -22
View File
@@ -35,7 +35,7 @@ bool Machine::create(const std::vector<uint8_t>& image, Machine* out,
// 数据区工作副本(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);
out->edge_prev_.assign(out->data_len() / 8 * 2, 0);
// MAIN 帧(跨周期保留)
const uint32_t entry = out->image_.header().entry_fn_id;
@@ -265,7 +265,12 @@ bool Machine::exec_one() {
return !ended_; // MAIN 的 RET → 周期结束(返回 false)
case isa::Op::CAL_TON:
case isa::Op::CAL_TOF:
case isa::Op::CAL_TP:
case isa::Op::CAL_CTU:
case isa::Op::CAL_CTD:
case isa::Op::CAL_CTUD:
case isa::Op::CAL_R_TRIG:
case isa::Op::CAL_F_TRIG:
fault_ = do_cal(static_cast<uint8_t>(op), isa::imm16(w));
return fault_ == Fault::None;
default:
@@ -340,15 +345,18 @@ Fault Machine::do_ret() {
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 字节定宽
// TON/TOF/TPin/pt/q/et(字段 0/1/2/3
// CTUcu/r/pv/q/cv0/1/2/3/4CTDcd/ld/pv/q/cv(同)
// CTUDcu/cd/r/lu/pv/qu/qd/cv0..7
// R_TRIG / F_TRIGclk/q0/1
// 字段偏移 = 字段序号 × 8(8 字节定宽槽);边沿存 edge_prev_
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
const bool is_ton = op == static_cast<uint8_t>(isa::Op::CAL_TON);
const bool is_tof = op == static_cast<uint8_t>(isa::Op::CAL_TOF);
if (is_ton || is_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;
@@ -356,7 +364,7 @@ Fault Machine::do_cal(uint8_t op, uint16_t slot) {
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)) {
if (is_ton) {
// TONin 真 → et += dt(到 pt 停)、q = et ≥ ptin 假 → et = 0、q = 0
if (in) {
et += dt;
@@ -383,29 +391,115 @@ Fault Machine::do_cal(uint8_t op, uint16_t slot) {
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 (op == static_cast<uint8_t>(isa::Op::CAL_TP)) {
// in=0 pt=1 q=2 et=3 + 上次 inedge_prev_[slot*2]
if (static_cast<size_t>(slot) * 2 + 1 >= edge_prev_.size()) return Fault::BadSlot;
if (!slot_get(static_cast<uint16_t>(slot + 0), &v)) return Fault::BadSlot;
const bool cu = v != 0;
const bool in = v != 0;
if (!slot_get(static_cast<uint16_t>(slot + 1), &v)) return Fault::BadSlot;
const bool r = v != 0;
const int64_t pt = v;
int64_t et = 0;
if (!slot_get(static_cast<uint16_t>(slot + 3), &et)) return Fault::BadSlot;
// TP(脉冲):in 上升沿启动 PT 时长的脉冲,期间 in 变化不影响
const bool prev = edge_prev_[slot * 2] != 0;
const bool rising = in && !prev;
int64_t q = 0;
if (rising) {
et = 0;
}
const bool timing = rising || (et > 0);
if (timing) {
et += dt;
if (pt > 0 && et >= pt) {
et = 0; // 脉冲结束:et 归零,下周期不再计时
q = 0;
} else {
q = 1;
}
} else {
et = 0;
q = 0;
}
edge_prev_[slot * 2] = in ? 1 : 0;
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) ||
op == static_cast<uint8_t>(isa::Op::CAL_CTD)) {
// CTUcu/r/pv/q/cvCTDcd/ld/pv/q/cv
if (static_cast<size_t>(slot) * 2 >= edge_prev_.size()) return Fault::BadSlot;
if (!slot_get(static_cast<uint16_t>(slot + 0), &v)) return Fault::BadSlot;
const bool edge_in = v != 0; // cuCTU/ cdCTD
if (!slot_get(static_cast<uint16_t>(slot + 1), &v)) return Fault::BadSlot;
const bool ld_in = v != 0; // rCTU/ ldCTD
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;
const bool prev = edge_prev_[slot * 2] != 0;
if (ld_in) {
cv = pv;
} else if (edge_in && !prev) {
cv += (op == static_cast<uint8_t>(isa::Op::CAL_CTU)) ? 1 : -1;
}
edge_prev_[slot * 2] = edge_in ? 1 : 0;
const int64_t q = (op == static_cast<uint8_t>(isa::Op::CAL_CTU))
? ((cv >= pv) ? 1 : 0)
: ((cv <= 0) ? 1 : 0);
if (!slot_set(static_cast<uint16_t>(slot + 4), cv)) return Fault::BadSlot;
if (!slot_set(static_cast<uint16_t>(slot + 3), q)) return Fault::BadSlot;
return Fault::None;
}
if (op == static_cast<uint8_t>(isa::Op::CAL_CTUD)) {
// cu=0 cd=1 r=2 lu=3 pv=4 qu=5 qd=6 cv=7
if (static_cast<size_t>(slot) * 2 + 1 >= edge_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 cd = v != 0;
if (!slot_get(static_cast<uint16_t>(slot + 2), &v)) return Fault::BadSlot;
const bool r = v != 0;
if (!slot_get(static_cast<uint16_t>(slot + 3), &v)) return Fault::BadSlot;
const bool lu = v != 0;
if (!slot_get(static_cast<uint16_t>(slot + 4), &v)) return Fault::BadSlot;
const int64_t pv = v;
int64_t cv = 0;
if (!slot_get(static_cast<uint16_t>(slot + 7), &cv)) return Fault::BadSlot;
const bool prev_cu = edge_prev_[slot * 2] != 0;
const bool prev_cd = edge_prev_[slot * 2 + 1] != 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;
} else if (lu) {
cv = pv;
} else {
if (cu && !prev_cu) cv += 1;
if (cd && !prev_cd) cv -= 1;
}
edge_prev_[slot * 2] = cu ? 1 : 0;
edge_prev_[slot * 2 + 1] = cd ? 1 : 0;
const int64_t qu = (cv >= pv) ? 1 : 0;
const int64_t qd = (cv <= 0) ? 1 : 0;
if (!slot_set(static_cast<uint16_t>(slot + 7), cv)) return Fault::BadSlot;
if (!slot_set(static_cast<uint16_t>(slot + 5), qu)) return Fault::BadSlot;
if (!slot_set(static_cast<uint16_t>(slot + 6), qd)) return Fault::BadSlot;
return Fault::None;
}
if (op == static_cast<uint8_t>(isa::Op::CAL_R_TRIG) ||
op == static_cast<uint8_t>(isa::Op::CAL_F_TRIG)) {
// clk=0 q=1
if (static_cast<size_t>(slot) * 2 >= edge_prev_.size()) return Fault::BadSlot;
if (!slot_get(static_cast<uint16_t>(slot + 0), &v)) return Fault::BadSlot;
const bool clk = v != 0;
const bool prev = edge_prev_[slot * 2] != 0;
const int64_t q = (op == static_cast<uint8_t>(isa::Op::CAL_R_TRIG))
? ((clk && !prev) ? 1 : 0)
: ((!clk && prev) ? 1 : 0);
edge_prev_[slot * 2] = clk ? 1 : 0;
if (!slot_set(static_cast<uint16_t>(slot + 1), q)) return Fault::BadSlot;
return Fault::None;
}