初始化仓库
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
build/**
|
||||||
|
.vscode/**
|
||||||
+329
@@ -0,0 +1,329 @@
|
|||||||
|
# 子集 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. 寄存器机与指令
|
||||||
|
|
||||||
|
- 局部 / `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. 目录
|
||||||
|
|
||||||
|
```text
|
||||||
|
project.toml # files / gvl.file / 可选 io
|
||||||
|
globals.st # 第一版唯一 GVL
|
||||||
|
motor.st
|
||||||
|
main.st
|
||||||
|
compiler/ # lexer parser symbols checker codegen linker
|
||||||
|
vm/ # 扫描周期、寄存器、映像、FB
|
||||||
|
host/ # 喂 I、收 Q、Δt、回放
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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. 实现顺序
|
||||||
|
|
||||||
|
1. 冻 toml 字段:`files` / `entry` / `cycle_limit` / `dt_ms` / `gvl.file` / 可选 `io.*`
|
||||||
|
2. 冻关键字表 + 约 20 个样例程序
|
||||||
|
3. 冻约 20 条指令编码
|
||||||
|
4. 冻符号表字段:名字、种类、类型、地址、源文件
|
||||||
|
5. 递归下降解析 → 符号表与链接 → 最后写 VM 循环
|
||||||
Reference in New Issue
Block a user