Skip to content

Language reference: evaluation flow, functions, and function blocks

This is the reference for what runs inside a nautilus controller: how a scan evaluates your logic in each IEC 61131-3 language, and what every built-in operator, function, and function block does — its arguments, its result type, and its behavior. The compiler enforces everything described here; when logic doesn’t type-check (say, a numeric function used where a BOOL must flow), the result is a compile diagnostic on the offending line/rung, never a silent coercion.

Every task runs the classic PLC cycle on its interval:

read inputs → evaluate the program top to bottom → write outputs

All three languages end up as the same intermediate form, one hop at a time:

.st ─────────────────────────► IR (compiled, type-checked)
.fbd ── transpile ──► ST ──────► IR
.ld ── transpile ──► FBD ──► ST ─► IR

Because each hop preserves line maps, a diagnostic anywhere in that chain lands back on your source — an .ld error points at the rung, an .fbd error at the statement. The text file is always the source of truth; the graphical editors are projections that edit it structurally.

Statements evaluate top to bottom within a scan, so a value written by one rung/statement is visible to the ones below it in the same scan, and to everything on the next scan (that’s what makes the seal-in idiom work).

A rung is a boolean expression that reads left to right:

ElementTextMeaning
NO contactTagpasses power when Tag is TRUE
NC contact/Tagpasses power when Tag is FALSE
Parallel branch[ a | b ]OR of its legs; legs are series (AND) and nest freely
Function contactFN(args)passes power when the call yields TRUE — the function must return BOOL
Function blockinst:TYPE(args)power drives the block’s power-in pin; power continues from its power-out pin (table below)
Coil( Tag )Tag := the rung condition, every scan
Set coil( S Tag )latch: Tag := Tag OR condition
Reset coil( R Tag )unlatch: Tag := Tag AND NOT condition

Series elements AND together; a rung with only a coil is driven by the rail (TRUE). Multiple coils on one rung share the same condition.

The BOOL rule. Power is boolean, so anything that gates it must yield BOOL. Comparisons do: GT(TempC, 90.0) is a fine contact. Numeric functions don’t: ADD(TempC, 0.0) as a contact is a compile error (operator AND on BOOL and REAL, or cannot assign REAL to BOOL when it’s alone on the rung) — ADD does not “pass through” its input. Numeric functions belong inside a comparison’s arguments — GE(ADD(Base, Bias), Limit) — or in an FBD/ST program, where values rather than power flow between elements.

Reading FB outputs: any instance output is addressable as inst.Pin everywhere — GE(t2.ET, T#2S) as a contact, t2.Q as an operand, or from another task’s FBD/ST program. To capture an output into a variable at the call site, use the standard’s output binding: t2:TON(PT := T#5S, ET => Elapsed) — the one way ladder stores a non-BOOL (coils only assign BOOL). => works in ST and FBD calls too.

BOOL, INT, DINT, UINT, UDINT, WORD, REAL, LREAL, TIME, STRING, plus ARRAY[lo..hi] OF T and user TYPE ... STRUCT.

  • Integer kinds share one 64-bit runtime representation; REAL/LREAL are float64.
  • TIME counts milliseconds internally. Literals: T#500MS, T#5S, T#2M30S. TIME values compare with the ordinary comparisons.
  • Mixed numeric arguments promote to REAL; comparing or combining a STRING with a number is an error, not a coercion.

These are the FBD/ladder block names that lower to ST operators. “n-ary” means the block accepts 2+ inputs (the + pin in the FBD editor).

NameArgumentsResultBehavior
AND, OR, XORn-ary BOOL (or INT for bitwise)samelogical/bitwise
NOT1 BOOL/INTsamenegation/complement
ADDn-ary numericcommon typesum
SUB2 numericcommon typedifference
MULn-ary numericcommon typeproduct
DIV2 numericcommon typequotient; integer ÷0 yields 0 (scan keeps running)
MOD2 INTINTremainder; ÷0 yields 0
MOVE1 anysamepass-through assignment (FBD wiring aid)
GT, GE, LT, LE2 comparableBOOLordering (numeric or TIME)
EQ, NE2 comparableBOOLequality

The comparison row is the ladder-relevant one: those six are the functions that can gate power directly.

Stateless, callable from ST, FBD blocks, and (where they return BOOL — or inside arguments) ladder function contacts.

NameSignatureResultBehavior
SELSEL(G: BOOL, IN0, IN1)type of IN0/IN1binary selector: G=FALSE → IN0, G=TRUE → IN1
MUXMUX(K: INT, IN0, …, INn)common typeK picks the K-th input (0-based); an out-of-range K faults the scan — clamp K with LIMIT if it can wander
MIN, MAXn-ary numericcommon typesmallest / largest
LIMITLIMIT(MN, IN, MX)common typeIN clamped into [MN, MX]
NameSignatureResultBehavior
ABS1 numericsame typeabsolute value
SQRT, LN, LOG, EXP1 numericREALroot, ln, log₁₀, eˣ
EXPTEXPT(base, exp)REALbaseᵉˣᵖ
TRUNC1 REALINTtoward-zero truncation
SIN, COS, TAN1 numeric (radians)REALtrigonometry
ASIN, ACOS, ATAN1 numericREALinverse trig
ATAN2ATAN2(Y, X)REALquadrant-correct arctangent
NameSignatureResultBehavior
SHL, SHR(IN: INT/WORD, N: INT)same as INshift left / logical shift right (zero-fill)
ROL, ROR(IN, N)same as INrotate left / right

Caveat: the runtime’s integers are 64-bit and declared widths aren’t tracked, so rotates operate over 64 bits — a WORD you think of as 16 bits rotates as a 64-bit value.

All positions are 1-based (IEC convention); length/position arguments clamp to the string instead of faulting.

NameSignatureResultBehavior
LENLEN(IN)INTlength
LEFT, RIGHT(IN, L)STRINGfirst / last L characters
MIDMID(IN, L, P)STRINGL characters starting at position P
CONCATn-ary STRINGSTRINGconcatenation
INSERTINSERT(IN1, IN2, P)STRINGIN2 inserted into IN1 after position P
DELETEDELETE(IN, L, P)STRINGL characters removed starting at P
REPLACEREPLACE(IN1, IN2, L, P)STRINGL characters at P replaced by IN2
FINDFIND(IN1, IN2)INT1-based position of IN2 in IN1; 0 when absent or IN2 empty

Explicit, in the standard’s X_TO_Y naming — there are no implicit conversions across kinds:

ConversionNotes
INT_TO_REAL, REAL_TO_INTREAL→INT rounds to nearest
BOOL_TO_INT, INT_TO_BOOL0 ↔ FALSE, nonzero → TRUE
INT_TO_TIME, TIME_TO_INTthe INT is milliseconds
REAL_TO_TIME, TIME_TO_REALmilliseconds, rounded to nearest
INT_TO_STRING, REAL_TO_STRING, BOOL_TO_STRING, TIME_TO_STRINGformatting
STRING_TO_INT, STRING_TO_REAL, STRING_TO_BOOLparse; a non-parsing string is a runtime scan fault, so validate upstream

Stateful — declare an instance (VAR t1 : TON; END_VAR, or inline in a rung as t1:TON(...)), and each instance keeps its own state between scans. Outputs read as inst.Pin from any language.

TypeInputsOutputsBehavior
TONIN: BOOL, PT: TIMEQ: BOOL, ET: TIMEon-delay: Q rises after IN has been TRUE for PT; ET is elapsed
TOFIN, PTQ, EToff-delay: Q stays TRUE for PT after IN drops
TPIN, PTQ, ETpulse: rising IN produces a PT-wide TRUE pulse
CTUCU: BOOL, R: BOOL, PV: INTQ: BOOL, CV: INTcount rising CU edges; Q when CV ≥ PV; R resets
CTDCD: BOOL, LD: BOOL, PV: INTQ, CVcount down from PV (LD loads); Q when CV ≤ 0
CTUDCU, CD, R, LD, PVQU, QD, CVup/down counter
R_TRIGCLK: BOOLQ: BOOLQ for exactly one scan on CLK’s rising edge
F_TRIGCLKQone-scan pulse on the falling edge
SRS1: BOOL, R: BOOLQ1: BOOLset-dominant latch
RSS: BOOL, R1: BOOLQ1: BOOLreset-dominant latch

When a block sits in a rung, rung power drives one input and continues from one output; every other pin is passed (or read) by name in the parentheses:

TypePower inPower out
TON, TOF, TPINQ
CTUCUQ
CTDCDQ
R_TRIG, F_TRIGCLKQ
SRS1Q1
RSSQ1
user FUNCTION_BLOCKINQ

Passing the power pin explicitly in the argument list (t1:TON(IN := x)) is an error — power owns it.

User FUNCTIONs and FUNCTION_BLOCKs written in library files participate everywhere the built-ins do; see “Structuring logic” in the main README.