Skip to content

Commit 3d52e28

Browse files
committed
Add validated OpenPLC RTO and RES lowering
1 parent 329aeb0 commit 3d52e28

19 files changed

Lines changed: 4039 additions & 55 deletions

File tree

ARCHITECTURE.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@ native project-directory packager, with no direct CODESYS dependency or
9292
emitted CODESYS metadata. The native packager currently accepts only the
9393
evidenced local-BOOL serial and two-path parallel Ladder subset, canonical
9494
Rockwell `TON` and `TOF`/`.DN` pairs, and optional `%MD` `TON` elapsed-time
95-
telemetry. It
95+
telemetry. The evidenced adjacent Rockwell `RTO`/`.DN`/`RES` group is lowered
96+
through a generated, target-owned `TF_RTO` compatibility function block. It
9697
rejects other semantics before writing a partial project. Native OpenPLC
9798
editor/runtime compatibility is expanded only after editor, compiler, and
9899
runtime evidence—not by inferring that superficially similar blocks are

docs/experiments/OpenPLC-native-project-compatibility.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,40 @@ opened and compiled successfully in OpenPLC. Its runtime behavior also passed
270270
the immediate-on, five-second delayed-off, and cancellation checks without
271271
manual changes, validating the complete TwinForge generation path.
272272

273+
### Retentive timer and reset evidence
274+
275+
OpenPLC does not provide Rockwell's Ladder-only `RTO` and `RES` instructions
276+
as native IEC blocks. TwinForge therefore lowers the evidenced source group to
277+
a target-specific `TF_RTO` Structured Text function block. Its interface maps
278+
Rockwell timer members without adding target behavior to the neutral model:
279+
280+
| Rockwell | `TF_RTO` |
281+
| --- | --- |
282+
| `.EN` | `Enabled` |
283+
| `.TT` | `TT` |
284+
| `.DN` | `Q` |
285+
| `.ACC` | `ET` |
286+
| `.PRE` | `PT` |
287+
| `RES(timer)` | `RESET` |
288+
289+
The reference and independently generated projects compiled successfully.
290+
Runtime tests confirmed partial accumulation, retention while disabled,
291+
resumption from the retained value, latched completion, `Enabled` and `TT`
292+
state, and clearing of elapsed time and status through reset.
293+
294+
The initial lowering deliberately requires adjacent canonical source rungs:
295+
296+
```text
297+
XIC(enable)RTO(timer,?,?);
298+
XIC(timer.DN)OTE(output);
299+
XIC(reset)RES(timer);
300+
```
301+
302+
Other ordering or shared-reset arrangements are rejected rather than guessed.
303+
The source still contains genuine Rockwell `RTO` and `RES` instructions; the
304+
generated `TF_RTO` block is an explicitly identified compatibility mapping,
305+
not a claim that OpenPLC implements those Rockwell instructions natively.
306+
273307
The native project's PLCopen XML export should also be retained as comparison
274308
evidence. It can reveal semantic differences between TwinForge's generic XML
275309
and OpenPLC's serialization, even though it cannot currently be loaded through

docs/roadmaps/architecture-refactoring-roadmap.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -323,8 +323,11 @@ Deferred until another device profile or target exists:
323323
`TOF` declaration and block type
324324
- [x] Validate the TwinForge-generated `TOF` fixture through OpenPLC compile
325325
and runtime behavior tests
326-
- [ ] Establish the required retentive state and reset semantics before
327-
implementing Rockwell `RTO`/`RES` lowering
326+
- [x] Establish retained accumulator, enable, timing, done, pause/resume, and
327+
reset semantics for Rockwell `RTO`/`RES`
328+
- [x] Implement the evidenced adjacent `RTO`/`.DN`/`RES` pattern through the
329+
generated OpenPLC `TF_RTO` compatibility function block
330+
- [x] Compile and runtime-test the TwinForge-generated `RTO/RES` fixture
328331
- [ ] Revisit physical channel and CIP assembly entities when EDS or live
329332
evidence supports them
330333
- [ ] Review this document whenever a module gains a second independent
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"deviceBoard": "OpenPLC Runtime v3",
3+
"communicationPort": "",
4+
"runtimeIpAddress": "",
5+
"compileOnly": true,
6+
"communicationConfiguration": {
7+
"modbusRTU": {
8+
"rtuInterface": "Serial",
9+
"rtuBaudRate": "115200",
10+
"rtuSlaveId": null,
11+
"rtuRS485ENPin": null
12+
},
13+
"modbusTCP": {
14+
"tcpInterface": "Ethernet",
15+
"tcpMacAddress": "DE:AD:BE:EF:DE:AD",
16+
"tcpStaticHostConfiguration": {
17+
"ipAddress": "",
18+
"dns": "",
19+
"gateway": "",
20+
"subnet": ""
21+
}
22+
},
23+
"communicationPreferences": {
24+
"enabledRTU": false,
25+
"enabledTCP": false,
26+
"enabledDHCP": true
27+
}
28+
}
29+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[]
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
FUNCTION_BLOCK TF_RTO
2+
VAR_INPUT
3+
IN : bool;
4+
RESET : bool;
5+
PT : time;
6+
END_VAR
7+
8+
VAR_OUTPUT
9+
Q : bool;
10+
ET : time;
11+
Enabled : bool;
12+
TT : bool;
13+
END_VAR
14+
15+
VAR
16+
SegmentTimer : TON;
17+
RetainedTime : time := T#0s;
18+
RemainingTime : time := T#0s;
19+
WasEnabled : bool := FALSE;
20+
END_VAR
21+
22+
Enabled := IN;
23+
24+
IF RESET THEN
25+
SegmentTimer(IN := FALSE, PT := T#0s);
26+
RetainedTime := T#0s;
27+
RemainingTime := T#0s;
28+
ET := T#0s;
29+
Q := FALSE;
30+
Enabled := FALSE;
31+
TT := FALSE;
32+
WasEnabled := FALSE;
33+
ELSIF IN AND NOT Q THEN
34+
IF RetainedTime >= PT THEN
35+
RetainedTime := PT;
36+
ET := PT;
37+
Q := TRUE;
38+
TT := FALSE;
39+
SegmentTimer(IN := FALSE, PT := T#0s);
40+
ELSE
41+
RemainingTime := PT - RetainedTime;
42+
SegmentTimer(IN := TRUE, PT := RemainingTime);
43+
ET := RetainedTime + SegmentTimer.ET;
44+
IF SegmentTimer.Q THEN
45+
RetainedTime := PT;
46+
ET := PT;
47+
Q := TRUE;
48+
END_IF;
49+
TT := NOT Q;
50+
END_IF;
51+
ELSIF NOT IN THEN
52+
IF WasEnabled AND NOT Q THEN
53+
RetainedTime := RetainedTime + SegmentTimer.ET;
54+
IF RetainedTime >= PT THEN
55+
RetainedTime := PT;
56+
Q := TRUE;
57+
END_IF;
58+
END_IF;
59+
SegmentTimer(IN := FALSE, PT := T#0s);
60+
ET := RetainedTime;
61+
TT := FALSE;
62+
ELSE
63+
SegmentTimer(IN := FALSE, PT := T#0s);
64+
ET := RetainedTime;
65+
TT := FALSE;
66+
END_IF;
67+
68+
WasEnabled := IN;
69+
70+
END_FUNCTION_BLOCK

0 commit comments

Comments
 (0)