扩充内置功能块为 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` 分配。