Variables and references
How identifiers resolve
When the matiec ST resolver sees a name in code, it walks through scopes in this order:
- POU-local variables (declared in the POU’s
VAR/VAR_INPUT/VAR_OUTPUT/VAR_TEMPblock). Bare name, no qualifier. - 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:
| Category | Source of truth | Typical use |
|---|---|---|
Anvil | Auto-generated by the bus topology — one pool entry per FDD dataPoint per device | Hardware I/O. Anvil.Pfirsich.T_1 is button 1 on the Pfirsich device. |
Bellows | Pool variable with bellowsExport=true flag — automatically mirrored by the Bellows OPC-UA / Modbus-TCP gateway | HMI / SCADA exposure. Bellows.Pfirsich.T_1 is the same physical variable as Anvil.Pfirsich.T_1, accessed through the HMI gateway. |
GVL | Plain 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
initializeinstructions (Memory:feedback_namespace_hierarchy).
ForgeIEC notes
- Don’t try to create Anvil vars by hand. The MCP tool
project.write.add_variable scope_kind=anvilis intentionally rejected (FORGE_ERR_INVALID_INPUT). Anvil variables are FDD-driven — they appear when a device gets modules materialised viabus.replace_device/bus.add_moduleand 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_flagdocuments 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.