IEC 61131-3 Compliance Traceability¶
Standard: IEC 61131-3 Ed. 3.0 -- Programmable Controllers, Programming Languages Applicability: Procedural sequencing (SFC), step/transition logic (ST), control modules (FBD via cyclic scan runtime)
Overview¶
Cloud-Native DCS implements three of the five IEC 61131-3 programming languages:
| Language | Support | Usage |
|---|---|---|
| SFC (Sequential Function Chart) | Implemented | Procedural templates, recipe procedures |
| ST (Structured Text) | Implemented | Step actions, transition conditions; one of two co-equal editable projections of a control module's network |
| FBD (Function Block Diagram) | Implemented | Control modules (native cyclic scan runtime); the other projection of the same network |
| LD (Ladder Diagram) | Not supported | Out of scope -- not relevant to batch/DCS |
| IL (Instruction List) | Not supported | Deprecated in Ed. 3.0 |
A control module's logic is single-representation: it has exactly
one canonical, executable form (its function-block network), and ST and
FBD are two editable projections of it (ADR 0012).
The "author in the IEC 61131-3 language you know" language-choice claim
is therefore now true for control modules as well as for phase logic
(which interprets ST directly). The claim carries one honest caveat: a
control module's editable ST is the dataflow subset that maps onto a
function-block network: variable declarations, tag I/O, block
instantiations, expressions, function calls, and selection that compiles
to SEL/MUX/EN-gated blocks. General loops (FOR/WHILE/REPEAT),
CASE, and order-dependent sequencing are not part of the subset
(they have no dataflow drawing) and are rejected at author time. Full ST
remains available on the phase / SFC path. This subset boundary is
inherent to the languages (ST is a strict superset of FBD) and is the
same limit every mainstream PLC tool enforces.
Requirements Traceability¶
SFC (Section 6.7)¶
| Requirement | Description | Status | Implementation |
|---|---|---|---|
| 6.7.1 | Step types (initial, regular) | Implemented | InitialStep field on SFCStep; engine activates initial step on start |
| 6.7.2 | Transition conditions | Implemented | ConditionST field evaluated by ST interpreter each scan |
| 6.7.3 | Transition priority (top-to-bottom) | Implemented | Transitions sorted by priority ascending (lower number first); first one whose guard evaluates true fires. See pkg/sfc/engine.go. |
| 6.7.4 | Step activation/deactivation | Implemented | Active step set with activation time tracking |
| 6.7.5 | Action associations (N, P, S, R, L, D, SD, DS, SL qualifiers) | Deliberate deviation | We do not implement IEC 61131-3 action qualifiers. SFC actions in this DCS have temporal extent (multi-scan procedural ST with WAIT, MESSAGE, PROMPT), which the standard's scan-cyclic qualifier model does not describe. We provide a single per-step waitForAction boolean instead. Rationale and full execution model: Procedural SFC Dialect. Decision: #246. |
| 6.7.6 | Selective divergence/convergence | Implemented | SelectiveDivergence with priority-ordered branches |
| 6.7.7 | Simultaneous divergence/convergence | Implemented | SimultaneousDivergence activates all branches; convergence waits for all |
| 6.7.8 | SFC body (nested charts) | Implemented | SubChart field on SFCStep for recursive nesting |
| 6.7.9 | Timeout supervision | Implemented | Per-step TimeoutSeconds with timeout detection |
charge.T >= chargeTime, where chargeTime is a TIME parameter defaulted to T#30s.Structured Text (Section 7.3)¶
| Requirement | Description | Status | Implementation |
|---|---|---|---|
| 7.3.1 | Assignment statement | Implemented | := operator |
| 7.3.2 | IF/ELSIF/ELSE/END_IF | Implemented | Full conditional branching |
| 7.3.3 | CASE/OF/END_CASE | Implemented | Multi-branch integer/enum selection |
| 7.3.4 | FOR/TO/BY/END_FOR | Implemented | Counted loop with optional step size |
| 7.3.5 | WHILE/DO/END_WHILE | Implemented | Conditional loop with iteration limit (default 10000) |
| 7.3.6 | REPEAT/UNTIL/END_REPEAT | Implemented | Post-test loop; body runs at least once; iteration limit (default 10000) #362 |
| 7.3.7 | EXIT statement | Implemented | Breaks the innermost WHILE/FOR/REPEAT loop; runtime error if used outside a loop #362 |
| 7.3.8 | RETURN statement | Implemented | Ends ST program execution; legal at any nesting depth #362 |
| 7.3.9 | Expressions and operators | Implemented | Full operator precedence per Table 68 |
| 6.6.1 | Function call named parameters | Implemented | Func(name := expr) syntax; names stored in CallExpr.ArgNames |
| ext | CALL_SERVICE builtin (smart-device service invocation) |
Implemented | Dispatches an OPC UA Method bound via Unit.spec.serviceBinding; positional unit + service name, named inputs, returns typed result. One AuditRecord per invocation. #346 |
| ext | MTP_COMMAND / MTP_STATE / MTP_COMMAND_ENABLED builtins (MTP module service orchestration) |
Implemented | Drives a VDI 2658-4 module service declared via Unit.spec.serviceBinding: writes a Table 14 command word gated on the live CommandEn, selects a declared procedure, reads the reported state back. One AuditRecord per command; the reads emit none. #1379 |
Data Types (Section 6.3)¶
| Requirement | Description | Status | Implementation |
|---|---|---|---|
| 6.3.1 | BOOL | Implemented | TypeBool with TRUE/FALSE literals |
| 6.3.2 | INT | Implemented | TypeInt (Go int64) |
| 6.3.3 | REAL | Implemented | TypeReal (Go float64) |
| 6.3.4 | STRING | Implemented | TypeString with single-quoted literals |
| 6.3.5 | TIME | Implemented | TypeTime with T# literals (T#5s, T#1m30s) |
| 6.3.6 | DINT, LINT, UINT, etc. | Not supported | Only INT (64-bit signed) |
| 6.3.7 | Arrays, structs | Not supported | Out of scope for embedded ST |
Operator Precedence (IEC 61131-3 Table 68)¶
Implemented following the standard exactly:
| Precedence | Operators | Status |
|---|---|---|
| Highest | () parentheses |
Implemented |
NOT, unary - |
Implemented | |
*, /, MOD |
Implemented | |
+, - |
Implemented | |
<, >, <=, >= |
Implemented | |
=, <> |
Implemented | |
AND |
Implemented | |
XOR |
Implemented | |
| Lowest | OR |
Implemented |
Architecture Integration¶
Recipe (SFC) Procedural (SFC) Control (one network, FBD/ST views)
MasterRecipe Procedure ControlProgram (FB network)
ControlRecipe UnitProcedure FBD view ⇄ ST view (pkg/cmlogic)
Operation DI, DO, AI, AO
Phase PID, AND, OR
| | |
v v v
SFC Engine SFC Engine + ST FB Runtime (cyclic scan) + Drivers
(batch-operator) (procedural-operator) (unit-runtime)
- SFC Engine: Scan-cycle execution (200ms default), manages active steps, evaluates transitions via ST interpreter
- ST Interpreter: Evaluates step actions and transition conditions. I/O via
READ()/WRITE()context functions. Timing viaWAIT(T#duration)andWaitSeconds(n)with named parameter support - FB Runtime: IEC 61131-3 cyclic scan execution. Blocks execute in declared order each scan cycle, and protocol drivers bridge to physical I/O. Output blocks (
DO,AO) carry device-interlock support — anILCKinput /interlockAddressparameter forces the configured safe value while tripped, andPIDoffers aTRKintegrator-hold input plus aTRK_VALback-calculation output-tracking input (wired from theAO'sOUT) for bumpless transfer on interlock release (ADR 0007). Vendor-extension blocks beyond the standard set execute in the same scan — e.g.DEVSTATE, the derived device-state priority encoder (ADR 0018), whoseOUTis a STRING state word. A single block's runtime error degrades the scan without halting it (the faulting block is skipped and flagged, and healthy outputs keep regulating). Since ADR 0078 the faulted block names reachControlProgram.status.faultedBlocksand raise a High equipment alarm, still without touching the batch., and deliberate program removal drives outputs to their configured fail state,failState: safeValue(default) orholdLast(IEC 62443 SR 3.6, #972), an active drive in place of a passive freeze (ADR 0009). The interlock supports a first-class, time-boxed bypass evaluated in the scan (auto-expiring on the edge), exposed as theILCK_BYPASSEDoutput and gated/audited at the gateway via theinterlock:bypasspermission (ADR 0010). The full trip-bypass-re-arm lifecycle is on camera in Alarms and Interlocks and on the ISA-88 exception-handling section.AIcarries the other side of the same idea: it publishes a reading only if it stands behind it, holdingOUTat the last trusted value and raising aPV_BADoutput when the driver reports bad quality or the raw value falls outside a configured fail band, andPID'sPV_BADinput folds that into the same tracking path so a dead instrument holds the loop where a bare reading would have driven it to a rail (ADR 0074).DIdoes the same for a digital input, minus the fail band, which cannot express anything over a two-valued signal: a broken contact reads exactly like an open one, so the driver's quality is the only evidence a digital failure produces (#1998). The sameTRK/TRK_VALback-calculation also gives bumpless transfer for a Manual-mode operator override. A readwrite tag bound toPID's ownOUTport (pid-loop/pid-cascade'sCV) is written the same way any readwrite computed tag is, and only reaches the block while the ControlModule is in Manual mode (ISA-88 Table 1, #1255).PIDdetects the override on its own output viaFBContext.SelfOverriddenand tracks it exactly like a forced value. On return to Automatic mode the gateway callsRuntime.ReleaseManualOverrides, which clears the override on every block implementingManualOverridableand hands control back to the loop's own computation (#1715) - Block parameter validation: a parameter absent from a document, or present with an empty value, takes the block's documented default. A parameter carrying a value that does not parse as its declared type refuses the load, so the program never reaches the scan (#1663, ADR 0064). The two used to be answered identically for
AI's engineering range,AO's output range andPID's gains. A mistyped bound or gain became the default, with nothing recording it. An engineering range is also declared whole or left entirely to driver discovery, since one bound alone would leave the runtime enforcing an interval the document does not state
Migration Status (IEC 61499 → IEC 61131-3)¶
| Phase | Scope | Status |
|---|---|---|
| Phase 1 | Replace FBNetwork / FBBlockType CRDs with ControlProgram / FunctionBlockType; cyclic-scan runtime; ~30 blocks ported; remove ECC/event-driven helpers from runtime |
Complete |
| Phase 2 | Cut controllers, unit-runtime adapter, gateway, and CLI over to the new types; drop dead legacy fallbacks and event/ECC DTOs; consolidate TagMapEntry.NetworkRef into ProgramRef |
Complete (#360) |
| Phase 3 | Unit-runtime HTTP API cutover to ControlProgram/FunctionBlockType shape | Complete (#361) |
| Phase 4 | ST language completeness (REPEAT/EXIT/RETURN, user-defined function blocks) | REPEAT/EXIT/RETURN complete (#362); user-defined function blocks still pending |
Phase 2 notes:
- Gateway-facing UI/CLI URL paths (
/api/v1/sites/{site}/fbnetworks,/api/v1/fbblocktypes,fbNetworkRefJSON tag) keep their pre-migration vocabulary so existing UI and CLI clients work unchanged. Only Go identifiers and CRD-internal field names moved to the IEC 61131-3 lexicon. dcs_cm_fb_network_runningPrometheus metric name preserved for dashboard continuity. The underlying gauge is nowmetrics.CMControlProgramRunningGauge.
Phase 3 notes:
- The internal runtime HTTP API (control-operator → unit-runtime) was
cut over to ControlProgram terminology:
/api/v1/controlprogramsand wire fieldconnections(wasdataConns). The cutover also renamed the singleton persisted state filelast-network.jsontolast-program.json. Dropping the legacy oneshot routes below removed its only writer. The reader was deleted in #1748, and the runtime restores its programs fromnetworks/all.json. Seedocs/architecture.md§ "IEC 61131-3 Phase 3 cutover" for the migration procedure. - The legacy oneshot routes (
POST /api/v1/fbnetwork,/status,/stop) were dropped. They were unused outside of legacy tests.
Gap Analysis¶
Implemented¶
- Core SFC execution with steps, transitions, divergence/convergence
- ST with all basic data types, operators, and control structures
- FBD via native cyclic scan runtime
- Composite (derived) function block types. A
FunctionBlockTypedeclares a sub-network of blocks behind named data ports, and the runtime flattens it into the program when it loads (ADR 0065) - A parameter interface on a composite type.
spec.parametersdeclares each configuration value an instance may set, and an inner block reads one as{{.params.<name>}}. A type that wraps I/O therefore serves any number of devices (#1660) - Scan-cycle timing, hold/resume, abort, timeout
- Warm restart of a running program. The unit runtime redeploys its programs
from disk after a process restart, and restores each block's operating point
with them. The first scan writes a continuation, where it used to write a
compile-time default
(ADR 0080).
A device output's operating point includes what it is doing about its own
write. An output that adopted the plant's value on a promotion goes on
writing that across the restart. One that has never been told anything goes
on writing nothing
(ADR 0082).
A regulating block above such an output tracks it for as long as it holds,
so the loop resumes on the plant's own value and not on an integrator it
wound against a compile-time setpoint
(#1815).
This is not the standard's
RETAINqualifier.RETAINis declared per variable by the author, and what is preserved here is chosen per block type by the runtime: the four blocks that decide what reaches the field carry their operating point, and the sequencing blocks deliberately do not. A declared retentive qualifier is in the Planned list below - Validation for both ST expressions and SFC chart structure
- CLI commands for control API group:
dcs create/delete/get controlprogram,dcs create/delete/get functionblocktype
Planned¶
RETAINandNON_RETAINvariable qualifiers, so an author declares what survives a restart. Today the runtime's per-block-type choice decides it- User-defined function blocks in ST
- Online change (modify running SFC)
- SFC diagnostic and monitoring per Section 6.7.10
Out of Scope¶
- Ladder Diagram (LD) -- not relevant to batch/DCS applications
- Instruction List (IL) -- deprecated in IEC 61131-3 Ed. 3.0
- Derived data types (arrays, structs, enums)
- Multiple integer widths (DINT, LINT, UINT, etc.)
- Function/function block declaration in ST (control-module ST is the dataflow projection of the FB network and no place to declare functions)
Deliberate deviations¶
These are places where we knowingly diverge from IEC 61131-3 because the standard's model does not fit the DCS's procedural-SFC use case. Captured here so auditors and recipe authors can find the rationale in one place.
-
Action qualifiers (§6.7.5 / §2.2.4.4). We do not implement N, P, S, R, L, D, SD, DS, or SL qualifiers. Our SFC actions are multi-scan procedural ST programs with
WAIT,MESSAGE, andPROMPTbuiltins. The standard's scan-cyclic action model does not describe such programs. We replaced the qualifier letters with a single per-stepwaitForActionboolean that honestly describes what the engine does. Decision recorded in issue #246. Full execution model: Procedural SFC Dialect. -
Timer and counter presets are configuration. The standard declares
PTonTON/TOF/TPandPVonCTU/CTDas input variables of the function block, so a conforming implementation re-reads them on every invocation and a preset may be driven from elsewhere in the program. The blocks inpkg/fbruntime/blocksreadptandpvonce, when the block is created, and never from the data bus. A preset is therefore fixed for the life of a deployed program, and changing one is a template edit and a redeploy. The ST authoring path enforces the restriction where it is written: a literal preset is recorded as a parameter, and a variable bound toPTorPVis rejected with a located diagnostic. Before issue #1655 it compiled to a data connection the block never read, which left the preset at zero with no diagnostic anywhere. A wait that has to be computed belongs on the phase or SFC path, which interprets ST directly.