577 lines
19 KiB
Markdown
577 lines
19 KiB
Markdown
# StScan:子集 ST + toml 工程 + 寄存器 VM
|
||
|
||
独立方案说明。调研全文见 `PLC-DSL.md`。
|
||
|
||
**定案:** IEC 风格的子集 ST,用 `.toml` 组织多个 `.st`,编译成单一映像,由寄存器虚拟机按扫描周期执行。
|
||
|
||
---
|
||
|
||
## 1. 做什么、不做什么
|
||
|
||
```text
|
||
多个 .st 源文件
|
||
↑ 由 project.toml 列出
|
||
↓
|
||
编译器(词法/语法 → 符号表 → 类型检查 → 寄存器码 → 链接)
|
||
↓
|
||
单一映像(函数字节码 + 全局/FB 数据槽 + 工程哈希)
|
||
↓
|
||
寄存器虚拟机,按扫描周期执行
|
||
```
|
||
|
||
**做**
|
||
|
||
- 子集 ST(不是完整 IEC)
|
||
- `.toml` 管工程,不声明变量
|
||
- 寄存器 VM + 扫描周期
|
||
- 有界 `CALL` / `RET`
|
||
- 全局只在指定 GVL 文件里声明
|
||
|
||
**不做**
|
||
|
||
- 「很像 ST 的 Python」
|
||
- 完整 IEC:指针、OOP、`ANY`、`VAR_IN_OUT`、间接寻址
|
||
- CPython 特化 / JIT / 真 Python 模块系统
|
||
- 第一版不做梯形图编辑器(以后编译到同一套 IR)
|
||
|
||
指令表可作为内部 IR 的文本形态,不必当主语言。
|
||
|
||
---
|
||
|
||
## 2. 执行模型
|
||
|
||
```text
|
||
每个扫描周期:
|
||
1. 按 toml 的 io 绑定,采样 → I / 输入全局
|
||
2. 从 PROGRAM MAIN 的 pc=0 执行到 RET
|
||
3. 写回 Q / 输出全局
|
||
4. 用本周期 Δt 推进 TON / TOF / CTU(不用 wall clock)
|
||
```
|
||
|
||
运行时是 **一台 VM、一个入口、一份映像**。多文件只影响源码怎么切、怎么链接。
|
||
|
||
---
|
||
|
||
## 3. 工程文件(toml)
|
||
|
||
toml **不声明 ST 变量**。只做三件事:列出源文件、指定唯一 GVL、(可选)把已声明的全局接到硬件。
|
||
|
||
```toml
|
||
[project]
|
||
name = "line1"
|
||
entry = "program MAIN"
|
||
cycle_limit = 100000
|
||
dt_ms = 10
|
||
|
||
[files]
|
||
st = ["globals.st", "motor.st", "main.st"]
|
||
|
||
# 全工程只允许这一处出现 VAR_GLOBAL(第一版:单个文件)
|
||
[gvl]
|
||
file = "globals.st"
|
||
|
||
# 可选:I/O 绑定。不创造变量,var 必须已在 GVL 里声明
|
||
[[io.input]]
|
||
var = "EmergencyStop"
|
||
channel = 0
|
||
bit = 2
|
||
|
||
[[io.input]]
|
||
var = "I0_0"
|
||
channel = 0
|
||
bit = 0
|
||
|
||
[[io.output]]
|
||
var = "Q0_0"
|
||
channel = 0
|
||
bit = 0
|
||
```
|
||
|
||
规则:
|
||
|
||
- 所有源文件编译前已知;无运行时加载。
|
||
- `gvl.file` 自动并入编译集合(已在 `files.st` 里则不重复)。
|
||
- 文件集合排序后算 **工程哈希**,写入映像,回放时槽位一致。
|
||
- 同名 `VAR_GLOBAL` 链接失败,禁止覆盖。
|
||
- `[[io.*]]` 的 `var` 未在 GVL 中出现 → 编译错误。
|
||
- 第一版禁止:模块级副作用、实例/函数体循环调用、函数指针、无界递归。
|
||
|
||
不要在 toml 里用 `[[globals]] name/type/init` 再声明变量。
|
||
名字和类型属于 ST;针脚和通道属于工程。
|
||
|
||
---
|
||
|
||
## 4. ST 子集(第一版)
|
||
|
||
**保留**
|
||
|
||
| 类别 | 内容 |
|
||
|---|---|
|
||
| POU | `PROGRAM` / `FUNCTION` / `FUNCTION_BLOCK` |
|
||
| 变量段 | `VAR` / `VAR_INPUT` / `VAR_OUTPUT` / `VAR_GLOBAL` / `VAR_EXTERNAL` |
|
||
| 类型 | `BOOL`、`INT`、`TIME`(以后再加 `DINT` / 定点数或 `REAL`) |
|
||
| 运算 | `:=`,`AND` `OR` `NOT`,比较,算术 |
|
||
| 控制 | `IF / ELSIF / ELSE / END_IF`,`WHILE … END_WHILE` |
|
||
| 功能块 | 内置 `TON` / `TOF` / `CTU`;`fb(in := …)`,字段 `fb.Q` |
|
||
|
||
`WHILE` 不做终止证明,靠 `cycle_limit` 卡死。
|
||
`AND` / `OR` **必须短路**,编成跳转;两边都算会让定时器、FB 多执行。
|
||
|
||
**第一版不做:** 指针、`REF`、接口、类、`VAR_IN_OUT`、`ANY`、间接寻址、用户类型套娃、`FUNCTION` 改全局。
|
||
|
||
---
|
||
|
||
## 5. 全局变量
|
||
|
||
ST 有全局:`VAR_GLOBAL` 声明,`VAR_EXTERNAL` 引用。`I` / `Q` / `M` 也是全局映像。
|
||
`FUNCTION` 第一版禁止写全局。
|
||
|
||
### 5.1 在哪个文件声明
|
||
|
||
**第一版:只有 `globals.st` 可以出现 `VAR_GLOBAL`。**
|
||
多文件指 PROGRAM / FB / FUNCTION 分文件,不是全局可以散落声明。
|
||
|
||
| 种类 | 放哪 | 几个文件 |
|
||
|---|---|---|
|
||
| `VAR_GLOBAL` / `CONSTANT` / `RETAIN` | `globals.st` | **1 个** |
|
||
| `I` / `Q` / `M` | 同一份 `globals.st` | 第一版不拆 |
|
||
| FB / PROGRAM 的 `VAR` | 各自的 `.st` | 多个(局部 / 实例) |
|
||
| `VAR_EXTERNAL` | 各 POU 的 `.st` | 多个(只引用) |
|
||
|
||
其他文件写 `VAR_GLOBAL` → 编译错误。
|
||
|
||
单文件的好处:声明顺序 = 地址顺序,不必再规定多 GVL 的排序和回放。
|
||
|
||
以后要拆,仍须 toml **显式列出全部 GVL**,按类别拆,不按功能块拆:
|
||
|
||
```toml
|
||
[gvl]
|
||
files = ["io.st", "globals.st"]
|
||
```
|
||
|
||
```text
|
||
io.st 只放 I/Q/M (以后才拆)
|
||
globals.st 只放命名全局
|
||
motor.st 只有 FB + VAR_EXTERNAL
|
||
main.st 只有 PROGRAM
|
||
```
|
||
|
||
不要 `motor_globals.st`、`pump_globals.st` 各写一套同名点。
|
||
|
||
### 5.2 寿命
|
||
|
||
| 种类 | 寿命 |
|
||
|---|---|
|
||
| `VAR` / `VAR_TEMP` | 本次调用或该 POU 内部 |
|
||
| FB 的 `VAR` | 跟着实例(各一份) |
|
||
| `VAR_GLOBAL` | 全工程一份 |
|
||
| `I/Q/M` | 全局,绑定 I/O 映像 |
|
||
|
||
`VAR_EXTERNAL` 接到已有槽,不是新变量。
|
||
第一版按 `globals.st` 声明顺序定址;以后若 `[gvl] files`:先 toml 列表,再文件内顺序。
|
||
|
||
### 5.3 编译器数据流
|
||
|
||
```text
|
||
project.toml
|
||
files.st → 要编译的源
|
||
gvl.file → 唯一允许 VAR_GLOBAL 的文件
|
||
io.* → 名字 → 硬件(可选,不创造变量)
|
||
|
||
globals.st
|
||
VAR_GLOBAL / CONSTANT / I/Q/M → 符号表 + 固定槽位
|
||
|
||
motor.st / main.st
|
||
VAR_EXTERNAL → 同一槽
|
||
```
|
||
|
||
---
|
||
|
||
## 6. 多文件导出什么
|
||
|
||
| 导出 | 规则 |
|
||
|---|---|
|
||
| `FUNCTION` | 无内部状态;寄存器帧在有界调用栈上 |
|
||
| `FUNCTION_BLOCK` | 必须先声明实例 |
|
||
| `VAR_GLOBAL`、`I/Q/M` | 全工程一份,只在 GVL 里声明 |
|
||
| 常量 | 进常量表 |
|
||
|
||
`CALL` 目标都是立即数 `fn_id`,没有函数指针。
|
||
|
||
---
|
||
|
||
## 7. 寄存器机与指令
|
||
|
||
编码宽度、操作码取值、映像头与定宽类型以 [`isa/指令与映像.md`](isa/指令与映像.md) 为准。本节只列助记符。
|
||
|
||
- 局部 / `VAR` = 固定寄存器;表达式临时值往后编号;`nregs` 写进函数头。
|
||
- `I/Q/M` 走映像指令,不是通用寄存器。
|
||
- 每个 FB 实例一块固定布局,字段偏移编译期算死。
|
||
|
||
```text
|
||
MOVE rd, rs
|
||
LOADK rd, const_id
|
||
NOT rd, rs
|
||
AND/OR rd, ra, rb (* 已算好的值;短路用跳转 *)
|
||
ADD/SUB/MUL/DIV rd, ra, rb
|
||
CMP_xx rd, ra, rb (* 结果 BOOL *)
|
||
JMP offset
|
||
JT/JF r, offset
|
||
LOAD_I / STORE_Q / LOAD_M / STORE_M
|
||
LOAD_GLOBAL / STORE_GLOBAL
|
||
CAL_TON / CAL_TOF / CAL_CTU
|
||
CALL fn_id
|
||
RET
|
||
```
|
||
|
||
寄存器机不会比栈机更确定。确定性来自类型、内存、时间和副作用。
|
||
|
||
---
|
||
|
||
## 8. 确定性与可控
|
||
|
||
- 同一程序 + 同一组 `I` + 同一 `Δt` → `Q`、`M`、FB 状态完全一致。
|
||
- 无 GC、无堆、无线程;变量编译期全部分配。
|
||
- 定宽整数;溢出规则写死(饱和或报错,选一个)。
|
||
- 第一版不要 `REAL`。
|
||
- 每周期指令上限(如 `cycle_limit = 100000`),超时是故障。
|
||
- 可单步、可看寄存器和映像、可按录制的 `I` 回放。
|
||
|
||
---
|
||
|
||
## 9. 编译器
|
||
|
||
中等工程,不是算法难题。直通 pass,不上 LLVM:
|
||
|
||
```text
|
||
读 toml、收齐 .st
|
||
→ 词法 / 递归下降语法
|
||
→ 收集导出符号
|
||
→ 解析 VAR_EXTERNAL、链接、定地址
|
||
→ 类型检查
|
||
→ 按 PROGRAM / FUNCTION / FB 编寄存器码
|
||
→ 回填跳转、写映像头与工程哈希
|
||
```
|
||
|
||
| 部分 | 难度 |
|
||
|---|---|
|
||
| ST 解析 | 中低 |
|
||
| 拒多余特性 | 低 |
|
||
| 多文件链接 | 中 |
|
||
| 类型检查 | 低~中 |
|
||
| 寄存器码(短路、IF/WHILE) | 中 |
|
||
| FB 布局与调用 | 中 |
|
||
| VM | 低 |
|
||
|
||
第一版不做:图染色分配、SSA、证明 WHILE 有界、异常表、闭包、优化。
|
||
|
||
一人约 **4~8 周** 做出 `MAIN + 一个 Motor FB + I/O + 全局`。
|
||
|
||
---
|
||
|
||
## 10. 目录
|
||
|
||
仓库布局见根目录 `README.md`。实现时按四个 CMake 目标拆,样例不放仓库根:
|
||
|
||
```text
|
||
isa/ # 指令编码、映像头、定宽类型(compiler 与 vm 的合同)
|
||
compiler/ # lexer parser symbols checker codegen linker
|
||
vm/ # 扫描周期、寄存器、映像、FB
|
||
host/ # 喂 I、收 Q、Δt、回放
|
||
examples/line1/ # 第 11 节最小样例(给人看)
|
||
tests/ # 冻规则用的正例 / 负例
|
||
```
|
||
|
||
---
|
||
|
||
## 11. 最小样例
|
||
|
||
`project.toml` 见第 3 节。
|
||
|
||
```st
|
||
(* globals.st *)
|
||
VAR_GLOBAL
|
||
EmergencyStop : BOOL := FALSE;
|
||
I0_0 : BOOL;
|
||
I0_1 : BOOL;
|
||
Q0_0 : BOOL;
|
||
END_VAR
|
||
|
||
(* motor.st *)
|
||
FUNCTION_BLOCK MotorStarter
|
||
VAR_EXTERNAL
|
||
EmergencyStop : BOOL;
|
||
END_VAR
|
||
VAR_INPUT
|
||
start, stop : BOOL;
|
||
END_VAR
|
||
VAR_OUTPUT
|
||
Q : BOOL;
|
||
END_VAR
|
||
Q := start AND NOT stop AND NOT EmergencyStop;
|
||
END_FUNCTION_BLOCK
|
||
|
||
(* main.st *)
|
||
PROGRAM MAIN
|
||
VAR
|
||
starter : MotorStarter;
|
||
END_VAR
|
||
starter(start := I0_0, stop := I0_1);
|
||
Q0_0 := starter.Q;
|
||
END_PROGRAM
|
||
```
|
||
|
||
---
|
||
|
||
## 12. 实现顺序
|
||
|
||
原则:**先冻合同,再解析,再链接,最后写 VM**。`compiler` 与 `vm` 不互链,合同只进 `isa`。每一阶段先有可运行的测试,再进入下一阶段。一人约 4~8 周做到第 11 节 `line1`。
|
||
|
||
第一版不做:图染色、SSA、WHILE 有界证明、异常表、闭包、优化、`REAL`、独立 `stc.exe`。
|
||
|
||
---
|
||
|
||
### 12.0 骨架(约 1 天)
|
||
|
||
CMake 四个目标能空编译:`isa`、`compiler`、`vm`、`host`。`host` 先打印版本即可。
|
||
|
||
- 落下 `examples/line1/`:第 3 节 toml + 第 11 节三个 `.st`。
|
||
- `tests/` 接上 CTest,先放一个必过的空测试。
|
||
- 溢出策略当场选定并写进 `isa` 注释:**饱和**或**报错**,只留一种。
|
||
|
||
完成:`cmake --build` 过;`ctest` 能跑。
|
||
|
||
---
|
||
|
||
### 12.1 冻合同(约 2~3 天,先于任何解析器)
|
||
|
||
四张表写进代码或 `Doc/`,改表要同步改测试。未冻完不要写递归下降。
|
||
|
||
**toml 字段(只这些)**
|
||
|
||
| 字段 | 含义 | 缺省 / 约束 |
|
||
|---|---|---|
|
||
| `project.name` | 工程名 | 必填 |
|
||
| `project.entry` | 入口 | 第一版必须是 `program MAIN` |
|
||
| `project.cycle_limit` | 每周期指令上限 | 如 `100000`;超时是故障 |
|
||
| `project.dt_ms` | 本周期 Δt | 给 TON / TOF / CTU,不是 wall clock |
|
||
| `files.st` | 源文件列表 | 编译前全部已知 |
|
||
| `gvl.file` | 唯一允许 `VAR_GLOBAL` 的文件 | 已在 `files.st` 则不重复收录 |
|
||
| `[[io.input]]` / `[[io.output]]` | `var` + `channel` + `bit` | 可选;`var` 必须已在 GVL |
|
||
|
||
禁止 toml 里用 `[[globals]]` 再声明变量。
|
||
|
||
**关键字(第一版只认这些)**
|
||
|
||
`PROGRAM` `FUNCTION` `FUNCTION_BLOCK` `END_PROGRAM` `END_FUNCTION` `END_FUNCTION_BLOCK`
|
||
`VAR` `VAR_INPUT` `VAR_OUTPUT` `VAR_GLOBAL` `VAR_EXTERNAL` `END_VAR`
|
||
`BOOL` `INT` `TIME`
|
||
`IF` `ELSIF` `ELSE` `END_IF` `WHILE` `END_WHILE`
|
||
`AND` `OR` `NOT`
|
||
`TRUE` `FALSE`
|
||
`TON` `TOF` `CTU`
|
||
|
||
其余一律当标识符或直接拒绝(`VAR_IN_OUT`、`REF`、`CLASS` 等出明确错误)。
|
||
|
||
**符号表一行**
|
||
|
||
`name` / `kind` / `type` / `address` / `source_file`
|
||
`kind` 至少:`global` / `external` / `local` / `input` / `output` / `pou` / `fb_instance` / `const`。
|
||
|
||
**指令(进 `isa`,约 20 条)**
|
||
|
||
布局、操作码、映像、哈希、饱和以 [`isa/指令与映像.md`](isa/指令与映像.md) 为准。第 7 节只列助记符。
|
||
|
||
完成:`isa` 能编解码一条指令;实现头文件,不另写第三份规范。
|
||
|
||
---
|
||
|
||
### 12.2 约 20 个样例(与 12.1 同时列出,随后逐个点亮)
|
||
|
||
每个用例一个子目录(`project.toml` + `.st`)。先写文件和期望(通过 / 报哪类错),再实现。`examples/line1` 给人看;负例和切片放 `tests/`。
|
||
|
||
| # | 用例 | 期望 |
|
||
|---|---|---|
|
||
| 1 | 空 `PROGRAM MAIN` + `RET` | 通过 |
|
||
| 2 | `BOOL` 赋值、`TRUE`/`FALSE` | 通过 |
|
||
| 3 | `AND`/`OR` 短路(右侧有副作用时不执行) | 通过 |
|
||
| 4 | `IF` / `ELSIF` / `ELSE` | 通过 |
|
||
| 5 | `WHILE` 正常结束 | 通过 |
|
||
| 6 | `WHILE` 打满 `cycle_limit` | 运行时故障 |
|
||
| 7 | `INT` 四则 + 比较 | 通过 |
|
||
| 8 | `TIME` 字面量 | 通过 |
|
||
| 9 | GVL + `VAR_EXTERNAL` | 通过,同一槽 |
|
||
| 10 | 非 GVL 文件写 `VAR_GLOBAL` | 编译错误 |
|
||
| 11 | 同名 `VAR_GLOBAL` | 链接失败 |
|
||
| 12 | `io.var` 未在 GVL | 编译错误 |
|
||
| 13 | `FUNCTION` 纯计算 + `CALL` | 通过 |
|
||
| 14 | `FUNCTION` 写全局 | 编译错误 |
|
||
| 15 | FB 实例 + `fb.Q` + 命名输入 | 通过 |
|
||
| 16 | 未声明实例就调 FB | 编译错误 |
|
||
| 17 | `TON` + 固定 Δt | 到时 `Q` 为真 |
|
||
| 18 | `TOF` / `CTU` | 行为与 Δt 一致 |
|
||
| 19 | 函数体循环调用 / 无界递归 | 编译错误 |
|
||
| 20 | `line1`(第 11 节) | I 组合 → `Q0_0` 与急停语义一致 |
|
||
|
||
完成:目录和期望表齐;此时大部分还编不过,属正常。
|
||
|
||
---
|
||
|
||
### 12.3 toml 读取(`compiler`,约 2 天)
|
||
|
||
还不碰 `.st` 正文。
|
||
|
||
- 解析第 3 节字段;未知键报错。
|
||
- 拼出编译文件集合:`files.st` ∪ `gvl.file`,去重、记绝对/相对路径。
|
||
- 校验:缺文件、`entry` 不是 `program MAIN`、`io` 缺 `var`/`channel`/`bit`。
|
||
- 算出工程哈希(文件集合排序后);先对源文件算,映像还没有也没关系。
|
||
|
||
完成:对 `examples/line1/project.toml` 打出文件列表和哈希;缺文件的负例过。
|
||
|
||
---
|
||
|
||
### 12.4 词法(`compiler`,约 2 天)
|
||
|
||
- `(* *)` 注释、标识符、关键字、整数、`TIME` 字面量、`:=`、比较符、括号。
|
||
- 关键字与标识符分词;大小写规则写死(建议与 IEC 一样不敏感,实现时统一折成一种内部形)。
|
||
- 每个 token 带 `source_file` + 行列,供以后报错。
|
||
|
||
完成:把第 11 节三个文件 lex 成稳定 token 流;非法字符报错。
|
||
|
||
---
|
||
|
||
### 12.5 递归下降语法(`compiler`,约 4~6 天)
|
||
|
||
只出 AST,不分配寄存器、不写映像。
|
||
|
||
顺序:
|
||
|
||
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`
|
||
4. 语句:赋值、`IF`、`WHILE`、FB 调用 `fb(in := …)`、字段 `fb.Q`
|
||
5. 表达式:`NOT`、`AND`/`OR`(AST 里保留短路语义)、比较、加减乘除
|
||
|
||
拒绝时给明确错误,不要 silently 忽略:`VAR_IN_OUT`、指针/`REF`、`CLASS`、`ANY`、用户类型套娃。
|
||
|
||
完成:`line1` 三个文件能 parse;用例 10、14、19 若已能在语法/结构层拒绝则先红后绿。
|
||
|
||
---
|
||
|
||
### 12.6 符号表与链接(`compiler`,约 3~4 天)
|
||
|
||
```text
|
||
收集导出 → 校验 GVL → 解析 VAR_EXTERNAL → 定地址 → 校验 io.*
|
||
```
|
||
|
||
- 先扫完全部 POU 再解析引用,禁止边解析边执行。
|
||
- 仅 `gvl.file` 可出现 `VAR_GLOBAL`;声明顺序 = 全局槽顺序。
|
||
- `VAR_EXTERNAL` 接已有槽,种类/类型必须一致,不是新变量。
|
||
- `FUNCTION` 标成无状态;`FUNCTION_BLOCK` 必须先有实例才能调用。
|
||
- `CALL` 目标解析成 `fn_id`(立即数),无函数指针。
|
||
- 同名全局、未声明 `io.var`、重复 POU 名 → 失败。
|
||
- FB 实例:按类型算出固定布局(输入/输出/内部/内置 TON 等字段偏移)。
|
||
|
||
完成:用例 9~12、16 按表变绿;符号表能打印 `name kind type address file`。
|
||
|
||
---
|
||
|
||
### 12.7 类型检查(`compiler`,约 2 天)
|
||
|
||
- `:=`、比较、算术的类型规则;`AND`/`OR`/`NOT` 只吃 `BOOL`。
|
||
- `FUNCTION` 禁止写全局(含经 `VAR_EXTERNAL`)。
|
||
- FB 调用:命名输入类型匹配;`fb.Q` 等字段存在且类型对。
|
||
- 第一版无隐式宽化;`INT` 与 `TIME` 不混用。
|
||
|
||
完成:类型错误有稳定报文;合法 `line1` AST 带类型。
|
||
|
||
---
|
||
|
||
### 12.8 寄存器码(`compiler`,约 5~7 天)
|
||
|
||
局部和 `VAR` 固定寄存器;表达式临时值往后编号;`nregs` 写进函数头。跳转先留洞,再回填。`AND`/`OR` **必须编成跳转**,禁止两边都算。
|
||
|
||
建议按指令面切片,每片都能 `isa` 编解码、必要时用临时 VM 桩执行:
|
||
|
||
1. `MOVE` `LOADK` `RET`,空 `MAIN` 出映像(对应用例 1~2)
|
||
2. `LOAD_I` `STORE_Q` `LOAD_GLOBAL` `STORE_GLOBAL` `LOAD_M` `STORE_M`
|
||
3. `NOT`,短路 `AND`/`OR`(用例 3)
|
||
4. `CMP_*` `JMP` `JT`/`JF` → `IF`(用例 4)
|
||
5. `WHILE` + 映像里带上 `cycle_limit`(用例 5;超限等 VM)
|
||
6. `ADD`/`SUB`/`MUL`/`DIV`(用例 7~8)
|
||
7. `CALL fn_id` + 有界帧(用例 13)
|
||
8. FB 实例块 + 字段偏移;`CAL_TON` / `CAL_TOF` / `CAL_CTU`(用例 15、17、18)
|
||
|
||
完成:`line1` 能写出映像(函数字节码 + 全局/FB 槽 + 工程哈希);跳转回填无悬空。
|
||
|
||
---
|
||
|
||
### 12.9 VM 循环(`vm`,约 3~4 天)
|
||
|
||
此时才写执行循环。只认 `isa` 映像。
|
||
|
||
```text
|
||
采样 I → pc=0 执行到 RET → 写回 Q → 用本周期 Δt 推进 TON/TOF/CTU
|
||
```
|
||
|
||
- 译码 + 大 `switch`;寄存器文件按函数头 `nregs` 分配。
|
||
- 每周期计指令数,超过 `cycle_limit` 报故障(用例 6)。
|
||
- 调用栈有界;`CALL`/`RET` 恢复 `pc` 与帧。
|
||
- 无 GC、无堆、无线程。同一映像 + 同一组 `I` + 同一 `Δt` → `Q`/`M`/FB 一致。
|
||
- 内置 FB:`CAL_TON`/`CAL_TOF`/`CAL_CTU` 只用映像里的 Δt,不读系统时钟。
|
||
|
||
完成:对手工拼的小映像能跑赋值和跳转;再接编译器产出的映像。
|
||
|
||
---
|
||
|
||
### 12.10 host(约 2~3 天)
|
||
|
||
一条命令:读 toml → `compiler` → `vm` 跑 N 个周期。不拆第二个可执行文件。
|
||
|
||
- 按 `[[io.*]]` 从宿主缓冲采样 / 写回,不创造变量。
|
||
- 把 `dt_ms` 每周期传给 VM。
|
||
- 最小可观测:打印本周期 I/Q,或把寄存器/映像 dump 出来。
|
||
- 回放:读预先录制的 I 序列(文本即可),不接真实硬件。
|
||
|
||
完成:`host examples/line1/project.toml` 能编译并跑若干周期。
|
||
|
||
---
|
||
|
||
### 12.11 闭环与冻测试(约 1 周)
|
||
|
||
- 点亮第 12.2 节 20 个用例;负例检查报错类别,不查英文原文。
|
||
- `line1` 固定输入表:急停、start/stop 组合 → 期望 `Q0_0`。
|
||
- 回放:同一 I 序列跑两遍,槽位与哈希一致。
|
||
- 单步:能停在指令边界,看到寄存器和 I/Q/M(可先做打印,不必做 UI)。
|
||
|
||
完成标准(第一版可交付):**`MAIN` + 一个 Motor FB + I/O + 全局**,扫描周期可重复。
|
||
|
||
---
|
||
|
||
### 12.12 建议日历
|
||
|
||
| 周 | 做完 |
|
||
|---|---|
|
||
| 1 | 12.0~12.4:骨架、合同、样例清单、toml、词法 |
|
||
| 2 | 12.5:AST,能 parse `line1`,拒绝多余特性 |
|
||
| 3 | 12.6~12.7:链接、定址、类型、io 校验 |
|
||
| 4 | 12.8 前半 + 12.9 前半:赋值/短路/IF 出映像,VM 能跑 |
|
||
| 5 | 12.8 后半 + 12.10:CALL、FB、host 跑起来 |
|
||
| 6 | 定时器/计数器、io、Δt、`line1` 语义 |
|
||
| 7~8 | 20 用例、回放、单步、补漏 |
|
||
|
||
周 4 结束时就应能跑一个「读 I、写 Q」的假 MAIN。不要等 FB 和 TON 齐了才第一次执行。
|
||
|
||
---
|
||
|
||
### 12.13 依赖方向(全程遵守)
|
||
|
||
```text
|
||
host → compiler → isa
|
||
host → vm → isa
|
||
```
|
||
|
||
- 指令或映像布局有变:先改 `isa` 和编解码测试,再改两端。
|
||
- 新 ST 特性:先加 12.2 的用例行,再改关键字 / AST / 检查 / 码 / VM。
|
||
- 不要为了一个 pass 新建 CMake 库。
|