diff --git a/Doc/compiler/STCompiler使用说明.md b/Doc/compiler/STCompiler使用说明.md index 927e497..5f980c0 100644 --- a/Doc/compiler/STCompiler使用说明.md +++ b/Doc/compiler/STCompiler使用说明.md @@ -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`。 diff --git a/Doc/compiler/符号表与链接.md b/Doc/compiler/符号表与链接.md index f540ed9..90eceeb 100644 --- a/Doc/compiler/符号表与链接.md +++ b/Doc/compiler/符号表与链接.md @@ -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 按对齐规则算 +- 实例布局 = 类型字段表的一份拷贝;字段偏移 = 字段序号 × 8(8 字节定宽槽) ## 错误 diff --git a/Doc/compiler/词法.md b/Doc/compiler/词法.md index 908b864..f0dfef8 100644 --- a/Doc/compiler/词法.md +++ b/Doc/compiler/词法.md @@ -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)出明确错误。 diff --git a/Doc/isa/stb文件格式.md b/Doc/isa/stb文件格式.md index 7a32781..f7ccf98 100644 --- a/Doc/isa/stb文件格式.md +++ b/Doc/isa/stb文件格式.md @@ -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` 起) diff --git a/Doc/isa/指令与映像.md b/Doc/isa/指令与映像.md index a196a7d..3cb6867 100644 --- a/Doc/isa/指令与映像.md +++ b/Doc/isa/指令与映像.md @@ -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 实例固定布局,字段偏移编译期算死。 diff --git a/Doc/vm/扫描周期.md b/Doc/vm/扫描周期.md index 0af8f6b..c40ce26 100644 --- a/Doc/vm/扫描周期.md +++ b/Doc/vm/扫描周期.md @@ -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` 推进(每周期一次调用 = 推进一次),不读系统时钟 - TON:in 真 → et += dt(到 pt 停)、q = et ≥ pt;in 假 → et = 0 diff --git a/Doc/vm/指令执行.md b/Doc/vm/指令执行.md index 11b72e8..00f810d 100644 --- a/Doc/vm/指令执行.md +++ b/Doc/vm/指令执行.md @@ -80,7 +80,7 @@ STORE_* slot, rs → 写 data[slot*8 .. slot*8+8) = rs(8 字节原样) | `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) = rs(8 字节原样) - 每条指令执行后 `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/et,CTU = cu/r/pv/q/cv)。每次执行 = 推进一次,Δt = 映像头 `dt_ms`(不读系统时钟): +`CAL_* slot` 的 `slot` 是实例**基槽号**;字段偏移 = 字段序号 × 8(布局冻结:TON/TOF/TP = in/pt/q/et;CTU = cu/r/pv/q/cv;CTD = cd/ld/pv/q/cv;CTUD = cu/cd/r/lu/pv/qu/qd/cv;R_TRIG/F_TRIG = clk/q)。每次执行 = 推进一次,Δt = 映像头 `dt_ms`(不读系统时钟)。边沿检测的"上次输入"存 VM 侧 `edge_prev_`(每实例基槽 2 字节),跨周期保留: ```text CAL_TON: - in = 槽[in] 非零;pt = 槽[pt];et = 槽[et];q = 槽[q] in 为真:et += dt(et ≥ pt 时停在 pt);q = (et ≥ pt) ? 1 : 0 in 为假:et = 0;q = 0 CAL_TOF: in 为真:q = 1;et = 0 in 掉电:et += dt;et ≥ pt 时 q = 0 - (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 += dt;et ≥ pt → q = 0、et 归零(脉冲结束);否则 q = 1 + 非计时:et = 0;q = 0 + +CAL_CTU:cu 上升沿 → cv += 1;r → cv = 0;q = (cv ≥ pv) ? 1 : 0 +CAL_CTD:cd 上升沿 → cv -= 1;ld → cv = pv;q = (cv ≤ 0) ? 1 : 0 +CAL_CTUD:cu 上升沿 → cv += 1;cd 上升沿 → cv -= 1;r → cv = 0;lu → cv = pv; + qu = (cv ≥ pv) ? 1 : 0;qd = (cv ≤ 0) ? 1 : 0 +CAL_R_TRIG:q = clk 且上次 clk 假(上升沿) +CAL_F_TRIG:q = !clk 且上次 clk 真(下降沿) ``` ## 故障 diff --git a/Doc/初步计划.md b/Doc/初步计划.md index ab003b8..9771ebe 100644 --- a/Doc/初步计划.md +++ b/Doc/初步计划.md @@ -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 推进内置 FB(TON/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` 分配。 diff --git a/compiler/include/compiler/Lexer.h b/compiler/include/compiler/Lexer.h index 0d3b4bf..d66517f 100644 --- a/compiler/include/compiler/Lexer.h +++ b/compiler/include/compiler/Lexer.h @@ -46,7 +46,12 @@ namespace compiler { FALSE, TON, TOF, + TP, CTU, + CTD, + CTUD, + R_TRIG, + F_TRIG, // 字面量 IDENT, // text = 小写名 INT_LIT, // int_value diff --git a/compiler/src/Codegen.cpp b/compiler/src/Codegen.cpp index 4e911bf..eb18252 100644 --- a/compiler/src/Codegen.cpp +++ b/compiler/src/Codegen.cpp @@ -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; } diff --git a/compiler/src/Lexer.cpp b/compiler/src/Lexer.cpp index 85d0d1f..19ed538 100644 --- a/compiler/src/Lexer.cpp +++ b/compiler/src/Lexer.cpp @@ -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}, }; /** diff --git a/compiler/src/Linker.cpp b/compiler/src/Linker.cpp index e4fce44..da25153 100644 --- a/compiler/src/Linker.cpp +++ b/compiler/src/Linker.cpp @@ -96,24 +96,40 @@ namespace { return k == TypeKind::Bool || k == TypeKind::Int || k == TypeKind::Time; } - // 内建 FB 布局(冻结,见 Doc/compiler/符号表与链接.md) + // 内建 FB 布局(冻结,见 Doc/compiler/符号表与链接.md;12.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; } diff --git a/compiler/src/Parser.cpp b/compiler/src/Parser.cpp index 38a570d..5c71730 100644 --- a/compiler/src/Parser.cpp +++ b/compiler/src/Parser.cpp @@ -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)) { diff --git a/isa/include/isa/Op.h b/isa/include/isa/Op.h index e74b33e..cfa3d83 100644 --- a/isa/include/isa/Op.h +++ b/isa/include/isa/Op.h @@ -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(op); diff --git a/isa/src/Encode.cpp b/isa/src/Encode.cpp index f6f1391..9990d66 100644 --- a/isa/src/Encode.cpp +++ b/isa/src/Encode.cpp @@ -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(imm16(w))); break; diff --git a/tests/src/isa_test.cpp b/tests/src/isa_test.cpp index ce95a3b..6588332 100644 --- a/tests/src/isa_test.cpp +++ b/tests/src/isa_test.cpp @@ -54,9 +54,13 @@ static bool test_sat() { static bool test_op() { using namespace isa; CHECK(static_cast(Op::MOVE) == 0); - CHECK(static_cast(Op::RET) == 28); - CHECK(kOpCount == 29); - CHECK(std::strcmp(mnemonic(Op::CAL_CTU), "CAL_CTU") == 0); + CHECK(static_cast(Op::CAL_TON) == 24); + CHECK(static_cast(Op::CAL_F_TRIG) == 31); // 8 个 CAL_* 连续 24..31 + CHECK(static_cast(Op::CALL) == 32); + CHECK(static_cast(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(255)), "???") == 0); return true; } diff --git a/vm/include/vm/Machine.h b/vm/include/vm/Machine.h index 4ffdc1e..9a63669 100644 --- a/vm/include/vm/Machine.h +++ b/vm/include/vm/Machine.h @@ -108,7 +108,9 @@ namespace vm { isa::ImageView image_; // 映像只读视图 std::vector data_; // 数据区工作副本(8 字节定宽槽) std::vector frames_; // 调用栈([0] = MAIN,跨周期保留) - std::vector ctu_prev_; // CTU 上次 cu(按实例基槽索引,12.9 步骤 4) + // 边沿检测上次输入(每槽 2 字节:[slot*2] 第一边沿、[slot*2+1] 第二边沿; + // CTU/CTD/R_TRIG/F_TRIG 用第一、CTUD 用两个),跨周期保留 + std::vector edge_prev_; uint32_t pc_ = 0; // 当前 pc(相对本帧函数字节码段) uint32_t cycle_count_ = 0; // 本周期指令数 Fault fault_ = Fault::None; // 最近一次故障 diff --git a/vm/src/Machine.cpp b/vm/src/Machine.cpp index 24a61a2..8493be0 100644 --- a/vm/src/Machine.cpp +++ b/vm/src/Machine.cpp @@ -35,7 +35,7 @@ bool Machine::create(const std::vector& 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(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/TOF:in/pt/q/et(字段 0/1/2/3) - // CTU:cu/r/pv/q/cv(字段 0/1/2/3/4) - // 实例槽号从 slot(基槽)起;字段偏移 = 字段序号 × 8(8 字节定宽) + // TON/TOF/TP:in/pt/q/et(字段 0/1/2/3) + // CTU:cu/r/pv/q/cv(0/1/2/3/4);CTD:cd/ld/pv/q/cv(同) + // CTUD:cu/cd/r/lu/pv/qu/qd/cv(0..7) + // R_TRIG / F_TRIG:clk/q(0/1) + // 字段偏移 = 字段序号 × 8(8 字节定宽槽);边沿存 edge_prev_ const int64_t dt = image_.header().dt_ms; int64_t v = 0; - if (op == static_cast(isa::Op::CAL_TON) || - op == static_cast(isa::Op::CAL_TOF)) { - // 字段:in=0 pt=1 q=2 et=3 + const bool is_ton = op == static_cast(isa::Op::CAL_TON); + const bool is_tof = op == static_cast(isa::Op::CAL_TOF); + if (is_ton || is_tof) { + // in=0 pt=1 q=2 et=3 if (!slot_get(static_cast(slot + 0), &v)) return Fault::BadSlot; const bool in = v != 0; if (!slot_get(static_cast(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(slot + 3), &et)) return Fault::BadSlot; int64_t q = 0; - if (op == static_cast(isa::Op::CAL_TON)) { + if (is_ton) { // TON:in 真 → et += dt(到 pt 停)、q = et ≥ pt;in 假 → 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(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(isa::Op::CAL_TP)) { + // in=0 pt=1 q=2 et=3 + 上次 in(edge_prev_[slot*2]) + if (static_cast(slot) * 2 + 1 >= edge_prev_.size()) return Fault::BadSlot; if (!slot_get(static_cast(slot + 0), &v)) return Fault::BadSlot; - const bool cu = v != 0; + const bool in = v != 0; if (!slot_get(static_cast(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(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(slot + 3), et)) return Fault::BadSlot; + if (!slot_set(static_cast(slot + 2), q)) return Fault::BadSlot; + return Fault::None; + } + + if (op == static_cast(isa::Op::CAL_CTU) || + op == static_cast(isa::Op::CAL_CTD)) { + // CTU:cu/r/pv/q/cv;CTD:cd/ld/pv/q/cv + if (static_cast(slot) * 2 >= edge_prev_.size()) return Fault::BadSlot; + if (!slot_get(static_cast(slot + 0), &v)) return Fault::BadSlot; + const bool edge_in = v != 0; // cu(CTU)/ cd(CTD) + if (!slot_get(static_cast(slot + 1), &v)) return Fault::BadSlot; + const bool ld_in = v != 0; // r(CTU)/ ld(CTD) if (!slot_get(static_cast(slot + 2), &v)) return Fault::BadSlot; const int64_t pv = v; int64_t cv = 0; if (!slot_get(static_cast(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(isa::Op::CAL_CTU)) ? 1 : -1; + } + edge_prev_[slot * 2] = edge_in ? 1 : 0; + const int64_t q = (op == static_cast(isa::Op::CAL_CTU)) + ? ((cv >= pv) ? 1 : 0) + : ((cv <= 0) ? 1 : 0); + if (!slot_set(static_cast(slot + 4), cv)) return Fault::BadSlot; + if (!slot_set(static_cast(slot + 3), q)) return Fault::BadSlot; + return Fault::None; + } + + if (op == static_cast(isa::Op::CAL_CTUD)) { + // cu=0 cd=1 r=2 lu=3 pv=4 qu=5 qd=6 cv=7 + if (static_cast(slot) * 2 + 1 >= edge_prev_.size()) return Fault::BadSlot; + if (!slot_get(static_cast(slot + 0), &v)) return Fault::BadSlot; + const bool cu = v != 0; + if (!slot_get(static_cast(slot + 1), &v)) return Fault::BadSlot; + const bool cd = v != 0; + if (!slot_get(static_cast(slot + 2), &v)) return Fault::BadSlot; + const bool r = v != 0; + if (!slot_get(static_cast(slot + 3), &v)) return Fault::BadSlot; + const bool lu = v != 0; + if (!slot_get(static_cast(slot + 4), &v)) return Fault::BadSlot; + const int64_t pv = v; + int64_t cv = 0; + if (!slot_get(static_cast(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(slot + 4), cv)) return Fault::BadSlot; - if (!slot_set(static_cast(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(slot + 7), cv)) return Fault::BadSlot; + if (!slot_set(static_cast(slot + 5), qu)) return Fault::BadSlot; + if (!slot_set(static_cast(slot + 6), qd)) return Fault::BadSlot; + return Fault::None; + } + + if (op == static_cast(isa::Op::CAL_R_TRIG) || + op == static_cast(isa::Op::CAL_F_TRIG)) { + // clk=0 q=1 + if (static_cast(slot) * 2 >= edge_prev_.size()) return Fault::BadSlot; + if (!slot_get(static_cast(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(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(slot + 1), q)) return Fault::BadSlot; return Fault::None; }