Variables and references

How identifiers resolve

When the matiec ST resolver sees a name in code, it walks through scopes in this order:

  1. POU-local variables (declared in the POU’s VAR / VAR_INPUT / VAR_OUTPUT / VAR_TEMP block). Bare name, no qualifier.
  2. Three-level qualified name <Category>.<Group>.<Name> where Category ∈ { Anvil, Bellows, GVL } — that bypasses the POU-local lookup entirely and goes straight to the pool.

POU-local — bare name

VAR
    iCount : INT;
    tonStep : TON;
END_VAR

iCount := iCount + 1;        (* bare name = POU-local *)
tonStep(IN := xStart, PT := T#1s);

Three-level qualified name

ForgeIEC uses <Category>.<Group>.<Name> for every pool variable. The category disambiguates which namespace the reference goes through:

CategorySource of truthTypical use
AnvilAuto-generated by the bus topology — one pool entry per FDD dataPoint per deviceHardware I/O. Anvil.Pfirsich.T_1 is button 1 on the Pfirsich device.
BellowsPool variable with bellowsExport=true flag — automatically mirrored by the Bellows OPC-UA / Modbus-TCP gatewayHMI / SCADA exposure. Bellows.Pfirsich.T_1 is the same physical variable as Anvil.Pfirsich.T_1, accessed through the HMI gateway.
GVLPlain VAR_GLOBAL (no special binding)Scratchpad globals not tied to hardware. GVL.Internal.iCounter for cross-POU shared state.

Dual-namespace pool variables

A single pool entry can be referenced via TWO different prefixes simultaneously when both Anvil and Bellows apply. This is the ForgeIEC “dual-namespace pool variable” feature documented in the editor’s MCP initialize instructions:

(* Same physical pool entry, two reference forms *)
xLocal := Anvil.Pfirsich.T_1;     (* hardware-side button *)
xHmi   := Bellows.Pfirsich.T_1;   (* HMI-side button *)

(* Typical PLC_PRG pattern: OR a hardware press and a software press *)
bButtons.0 := Anvil.Pfirsich.T_1 OR Bellows.Pfirsich.T_1;

For dual access to work, the pool variable MUST carry the bellowsExport flag. When the resolver fails on Bellows.X.Y, the fix is almost never “add a new Bellows variable” — it is “set the bellows_export flag on the existing Anvil variable named Y in group X”.

Member access

Structures and FB instances expose members with .:

VAR
    tonStep : TON;
END_VAR

tonStep(IN := xStart, PT := T#1s);
xElapsed := tonStep.Q;            (* read FB output via instance.field *)
tElapsed := tonStep.ET;

Bit access

BYTE / WORD / DWORD / LWORD variables expose individual bits with .<index>:

xBit0 := bMask.0;     (* read bit 0 as BOOL *)
bMask.7 := TRUE;      (* write bit 7 *)

See bitwise operators for more.

Array indexing

arr[3] := 42;
iValue := arr[i];

Index expressions must be ANY_INT. matiec is strict on out-of-bounds at compile time when the index is a literal constant; runtime out-of-bounds is implementation-defined (matiec generates a wrap or fault depending on the build).

Case sensitivity

Per IEC 61131-3 §1.4.2, identifiers are case-insensitive. Anvil.Pfirsich.T_1, ANVIL.Pfirsich.T_1 and anvil.pfirsich.t_1 resolve to the same variable.

In ForgeIEC, the editor normalises display to mixed-case (Anvil.<Group>.<Name>) but accepts any case in source. The parameter names of FB calls (stepTimer(IN := x, PT := t)) are an exception — matiec is case-sensitive there. See function-calls/positional-vs-named.

IEC reference

  • Identifier rules: IEC 61131-3 third edition (2013), clause 1.4.2.
  • Variable scopes (VAR, VAR_GLOBAL, …): clause 6.5.
  • ForgeIEC three-namespace convention: editor MCP initialize instructions (Memory: feedback_namespace_hierarchy).

ForgeIEC notes

  • Don’t try to create Anvil vars by hand. The MCP tool project.write.add_variable scope_kind=anvil is intentionally rejected (FORGE_ERR_INVALID_INPUT). Anvil variables are FDD-driven — they appear when a device gets modules materialised via bus.replace_device / bus.add_module and disappear when the device is replaced.
  • Bellows export is a flag on the Anvil variable, not a separate variable type. Memory feedback_bellows_is_gvl_flag documents this.
  • The scope_path field on every pool variable (anvil.<group>.<name>, bellows.<group>.<name>, gvl.<namespace>.<name>, pou.<pou_name>.<name>) is the canonical lower-case key used by every MCP tool and by the cross-reference subsystem in the editor.