Skip to content

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.

One stored network, two editable projections held side by side: the interlocked-speed Function Block Diagram and the Structured Text view of the same logic.
The subset boundary enforced at author time: out-of-subset Structured Text is refused with a located error, and block execution order renumbers in place.

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
6.7.2 and 6.3.5: a transition condition authored against the standard's own vocabulary, charge.T >= chargeTime, where chargeTime is a TIME parameter defaulted to T#30s.
6.7.6 and 6.7.7: the divergence bar auto-created grouping both branches, born Simultaneous, then flipped to Selective with a condition per branch.

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 via WAIT(T#duration) and WaitSeconds(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 — an ILCK input / interlockAddress parameter forces the configured safe value while tripped, and PID offers a TRK integrator-hold input plus a TRK_VAL back-calculation output-tracking input (wired from the AO's OUT) 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), whose OUT is 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 reach ControlProgram.status.faultedBlocks and raise a High equipment alarm, still without touching the batch., and deliberate program removal drives outputs to their configured fail state, failState: safeValue (default) or holdLast (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 the ILCK_BYPASSED output and gated/audited at the gateway via the interlock:bypass permission (ADR 0010). The full trip-bypass-re-arm lifecycle is on camera in Alarms and Interlocks and on the ISA-88 exception-handling section. AI carries the other side of the same idea: it publishes a reading only if it stands behind it, holding OUT at the last trusted value and raising a PV_BAD output when the driver reports bad quality or the raw value falls outside a configured fail band, and PID's PV_BAD input 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). DI does 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 same TRK/TRK_VAL back-calculation also gives bumpless transfer for a Manual-mode operator override. A readwrite tag bound to PID's own OUT port (pid-loop/pid-cascade's CV) 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). PID detects the override on its own output via FBContext.SelfOverridden and tracks it exactly like a forced value. On return to Automatic mode the gateway calls Runtime.ReleaseManualOverrides, which clears the override on every block implementing ManualOverridable and 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 and PID'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
The cyclic scan running: live block I/O values overlaid on the interlocked-speed network, the device interlock ARMED, and every declared tag streaming beneath it.

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, fbNetworkRef JSON 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_running Prometheus metric name preserved for dashboard continuity. The underlying gauge is now metrics.CMControlProgramRunningGauge.

Phase 3 notes:

  • The internal runtime HTTP API (control-operator → unit-runtime) was cut over to ControlProgram terminology: /api/v1/controlprograms and wire field connections (was dataConns). The cutover also renamed the singleton persisted state file last-network.json to last-program.json. Dropping the legacy oneshot routes below removed its only writer. The reader was deleted in #1748, and the runtime restores its programs from networks/all.json. See docs/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 FunctionBlockType declares 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.parameters declares 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 RETAIN qualifier. RETAIN is 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

  • RETAIN and NON_RETAIN variable 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, and PROMPT builtins. The standard's scan-cyclic action model does not describe such programs. We replaced the qualifier letters with a single per-step waitForAction boolean 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 PT on TON/TOF/TP and PV on CTU/CTD as 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 in pkg/fbruntime/blocks read pt and pv once, 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 to PT or PV is 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.