Implementer extensions
IEC 61131-3 Ed.3 clause 5 c asks an implementation for “a document that specifies all Implementer specific extensions. These are any features accepted by the system that are prohibited or not specified.” This is that document for iecf.
By default iecf accepts none: it checks the standard as written. Each extension is switched on by name, for one run:
iecf check --extension implicit-externals FILE...
A program that needs an extension is not portable to another compliant tool as it stands; the section of each extension says what to change to make it so.
implicit-externals: a POU uses a global variable without declaring it VAR_EXTERNAL
The standard. Ed.3 6.5.2.2: global variables are “only accessible
to a program organization unit via a VAR_EXTERNAL declaration”, with
the global’s type, and CONSTANT if the global is. Without the
extension, a global used undeclared is E0010.
The extension. A name a POU does not declare, which a VAR_GLOBAL
of the configuration, of a resource or of a global variable list does,
is that global, as if the POU had declared it
VAR_EXTERNAL (VAR_EXTERNAL CONSTANT for a constant global). The
POU’s own declarations come first: a local of the same name hides the
global. Everything else about the global is checked as usual: its
type, and that a constant is not assigned to. It is compiled as the
VAR_EXTERNAL would be, in any PROGRAM, FUNCTION_BLOCK or FUNCTION; a
FUNCTION that writes a global so has effects, and its calls keep the
standard’s order of evaluation (dependencies).
Why. Some tools and editors keep a project’s globals in one list and
let every POU use them. A platform that wants that may switch this on,
or have its editor write the VAR_EXTERNAL declarations where the user
sees them; iecf supports both and decides neither.
To make a program portable. Add to each POU a VAR_EXTERNAL block
naming every global it uses, with the global’s type.
var-config-list: VAR_CONFIG outside a CONFIGURATION, paths from the PROGRAM’s name
The standard. Ed.3 6.8.2 and Annex A (Config_Decl): VAR_CONFIG
is part of a CONFIGURATION, after its resources; a path starts with
the resource (if the configuration names one), then the program
instance. Without the extension, a VAR_CONFIG at the top of a file is
E0025.
The extension. As CODESYS writes it: VAR_CONFIG ... END_VAR at the
top of any file, in as many blocks as wanted. Each PROGRAM is its own
one instance, under its own name, and a path starts there:
VAR_CONFIG
PLC_PRG.valve1.coil AT %QX0.0 : BOOL;
END_VAR
Everything else is checked as for the standard form: the path, the
repeated type, the address’s area, the initial values, and every *
address of every PROGRAM completed (E0023). A project that has a
CONFIGURATION keeps its VAR_CONFIG there; a list of its own is then
still E0025.
Why. CODESYS projects, and those of the systems built on CODESYS,
complete their AT %I* addresses this way: there is no textual
CONFIGURATION, tasks call PROGRAMs by name. (Beckhoff TwinCAT has no
VAR_CONFIG at all; it links %I* variables in its I/O configuration,
outside the source text, which iecf does not read: PROPOSAL.md §2b.)
To make a program portable. Write a CONFIGURATION with a resource,
its tasks and a program instance per PROGRAM, and move the entries into
its VAR_CONFIG, each path prefixed with the resource’s name.
time-integer-conversions: TIME and LTIME to and from integers, in ms and ns
The standard. Ed.3 Figure 11 defines no conversion between TIME or
LTIME and an integer or bit-string type (“-”), and Ed.2 named none.
Without the extension, DINT_TO_TIME, TIME_TO_UDINT, TO_TIME(i)
and the like are E0013. A duration is made from a number with TIME
arithmetic: T#1ms * n.
The extension. <int>_TO_TIME, TIME_TO_<int>, the same for LTIME,
the overloaded TO_TIME/TO_LTIME from an integer, and the same with
BYTE, WORD, DWORD and LWORD. A TIME converts as its count of
milliseconds, an LTIME as its count of nanoseconds: DINT_TO_TIME(1500)
is T#1s500ms, TIME_TO_DINT(T#2s) is 2000. A value that does not fit
the target keeps its low bits and is flagged, as any integer conversion
(docs/c-output.md §5).
Why. CODESYS, Beckhoff TwinCAT and Siemens all have these conversions, counting milliseconds; programs written for them use them. (matiec has them too, counting seconds: a program carried over from matiec waits a thousand times less here.)
To make a program portable. Replace DINT_TO_TIME(n) with
T#1ms * n (Table 35, MUL). The standard has no way back from a
duration to a number (Table 35 divides a TIME only by a number), so keep
a duration in a TIME throughout and compare it with TIME literals.
bit-string-to-bool: BYTE, WORD, DWORD and LWORD to BOOL: TRUE for any value but 0
The standard. Ed.3 contradicts itself. Figure 11 defines no
conversion from a bit string to BOOL (“-”); Table 25 lists
BYTE_TO_BOOL, WORD_TO_BOOL, DWORD_TO_BOOL and LWORD_TO_BOOL as
“Binary transfer of the rightmost bit into the target”. iecf follows
Figure 11: without the extension they, and TO_BOOL of a bit string,
are E0013.
The extension. They convert, and the result is TRUE for any value
but 0: BYTE_TO_BOOL(BYTE#2) is TRUE, where Table 25’s rightmost bit
would make it FALSE. Integers to BOOL stay refused.
Why. CODESYS and Beckhoff TwinCAT have these conversions and give TRUE for any value but 0; programs written for them use them (decided, florin, 2026-11-08: PROPOSAL.md §6.8).
To make a program portable. Write what is meant: b <> 0 for any
bit set, b.0 (Ed.3 6.5.3, bit access) for the rightmost bit.
reference-to: CODESYS’s REFERENCE TO, bound with REF=, tested with __ISVALIDREF
The standard. Ed.3 6.4.4.10 has one kind of reference, REF_TO,
taken with REF() and dereferenced with ^. REFERENCE TO, REF=
and __ISVALIDREF are not IEC 61131-3. Without the extension the type
is E0011 and REF= is E0005.
The extension. As CODESYS and Beckhoff TwinCAT have it:
r : REFERENCE TO Tis a reference that stands for what it references wherever it is used:r := 5,x := r + 1,r.f,r[i]andr(...)for a function block go through it, with no^.r REF= xbinds it tox, whichREF(x)could take (E0026);r REF= r2binds it to what the REFERENCE TOr2references;r REF= 0unbinds it. It starts unbound; it takes no initial value (E0013).__ISVALIDREF(r)is TRUE whenris bound.- A function block’s
VAR_INPUTof a REFERENCE TO is bound by a call,fb(r := x): by its argument, as REF= would. A call that does not pass it leaves it bound as it was, as an IEC input keeps its value. - No vendor page found says what a call that does not pass such an
input does, nor shows
r.f,r[i]orr(...)through a REFERENCE TO; both are as above, inferred from the rest (decided, florin, 2026-11-09). - An unbound REFERENCE TO used is a fault,
IECF_FAULT_NULL, as a NULLREF_TOdereferenced is (dependencies, References). - Where a
REF_TOcannot be, a REFERENCE TO cannot either: not a VAR_IN_OUT, not RETAIN, not at an address. Nor, as in CODESYS and TwinCAT, an array of REFERENCE TO, aREF_TOa REFERENCE TO, or a REFERENCE TO a reference (E0027).
In the C, a REFERENCE TO is the same iecf_ref_t as a REF_TO
(The C output §4), and its uses are generated as the
REF_TO ones they stand for: r as r^, r REF= x as
r := REF(x). The symbol table names its type REFERENCE TO T.
Why. Programs written for CODESYS and TwinCAT use REFERENCE TO for
what Ed.3 does with REF_TO (decided, florin:
PROPOSAL.md §5 step 3).
To make a program portable. Declare r : REF_TO T, write
r := REF(x) for r REF= x, r := NULL for r REF= 0,
r <> NULL for __ISVALIDREF(r), and r^ wherever r is used. A
function block’s REFERENCE TO input becomes a REF_TO input passed
REF(x).
global-namespace: _. names the global namespace, as Siemens and PLCnext write it
The standard. Ed.3 6.9.1 puts every element declared outside a
NAMESPACE, and every standard function and function block, in the
global namespace, but gives no way to name it. Its one example,
Ton2: PUBLIC.TON; (* uses the standard timer *) (6.9.3), cannot be
read: PUBLIC is an access specifier, and the grammar has no such name.
So a namespace that declares its own TON cannot reach the standard
one. Without the extension, _. is E0028.
The extension. As Siemens TIA Portal and PLCnext Engineer write it:
_. before a name looks it up in the global namespace only, past every
namespace the code is in and every USING (C#’s global::).
_.TONis the standard timer, from inside a namespace with its ownTON;_.Plant.Mainis the top-level namespacePlant’sMain, even inside a namespace that has aPlantof its own;_.g, a global variable, isg.
Why. Example 2 of Ed.3 6.9.2 declares a namespace’s own TON, “an implementation of TON with a new name”; inside that namespace the standard TON is then out of reach (NAMESPACES.md, D4).
To make a program portable. Give the namespace’s own element another name, so the standard one is reached by its short name.