Compose FB and SSFC Components¶
This guide covers the practical steps for wiring child components — both FUNCTION_BLOCK and SSFC — into parent FUNCTION_BLOCK, SSFC, and PROGRAM containers. It assumes you already know how to write individual components; the focus here is on the connection patterns.
Wire an FB inside a FUNCTION_BLOCK or PROGRAM (WIRING block)¶
The canonical way to host child components is with a top-level WIRING … END_WIRING block. The WIRING block declares the structural graph of the POU: which child instances receive which parent inputs, and which parent outputs receive which child outputs.
FUNCTION_BLOCK TemperatureController
VAR_INPUT
measurement : MATHREAL;
setpoint : MATHREAL;
enable : BOOL;
END_VAR
VAR_OUTPUT
output : MATHREAL;
END_VAR
VAR
pid : PID(Kp := 2.0, Ki := 0.1);
END_VAR
WIRING
pid(
measurement := measurement,
setpoint := setpoint,
enable := enable
);
output := pid.output;
END_WIRING
END_FUNCTION_BLOCK
The same pattern applies to PROGRAM:
PROGRAM Pasteurizer
VAR_INPUT
temperature : MATHREAL;
setpoint : MATHREAL;
start : BOOL;
level_high : BOOL;
END_VAR
VAR_OUTPUT
heater_command : MATHREAL;
valve_fill : BOOL;
END_VAR
VAR
sequence : BatchPasteurizer(HOLD_TIME := T#30s);
pid : TemperaturePID;
END_VAR
WIRING
sequence(start := start, level_high := level_high, ...);
pid(enable := sequence.heater, actual := temperature, ...);
heater_command := pid.Output;
valve_fill := sequence.valve_fill;
END_WIRING
END_PROGRAM
WIRING has two syntactic forms:
| Form | Syntax | What it does |
|---|---|---|
| Child-input binding | inst(Port := expr, ...); |
Wires expression values to child VAR_INPUT ports |
| Output forwarding | output := source; |
Forwards a named VAR_OUTPUT to a child output or local |
Output forwarding RHS must be a simple reference (local variable, instance output path, or VAR_INPUT passthrough) — not an arithmetic expression. Computed values belong in IMPLEMENTATION.
A PROGRAM that is a pure composition needs no IMPLEMENTATION block at all.
Key points:
PARAMETERvalues (Kp,Ki) are bound at declaration time inVAR, not inWIRING.- Every
VAR_INPUTport of the child must be wired. Omitting a port is a static error. - Output forwarding entries make the output visible in the schema and in tooling; they are equivalent to a named alias.
- Read child output ports via dot notation:
pid.output,sequence.valve_fill.
Common mistakes at this step:
- Binding a
PARAMETERinWIRINGinstead of in theVARdeclaration —PARAMETERis not a wiring target. - Placing computed logic (
IF … THEN …, arithmetic) inWIRING— move it toIMPLEMENTATION. - Omitting the output forwarding entries for VAR_OUTPUT — the schema will be missing outputs.
Wire an FB inside an SSFC¶
A child FUNCTION_BLOCK is wired in one WIRING block at the SSFC's own
level — not inside a state. WIRING is declarative: the binding holds in every
scan. What a state controls is the value wired to the child, not whether the
wiring exists.
State-dependent values come from the sequence itself. Give the SSFC an output that says what it wants, set it in the states, and wire that output to the child:
SSFC PasteurizerSequence
VAR_INPUT
abort : BOOL;
END_VAR
VAR_OUTPUT
heat_on : BOOL; (* what the sequence WANTS, not how it is achieved *)
END_VAR
INITIAL STATE Idle
EN: heat_on := FALSE;
TRANSITION TO Heating WHEN NOT abort;
END_STATE
STATE Heating
EN: heat_on := TRUE;
EX: heat_on := FALSE;
TRANSITION TO Idle WHEN abort;
END_STATE
END_SSFC
The composition root wires the two together:
PROGRAM Plant
VAR_INPUT
temp : INT;
target : INT;
abort : BOOL;
END_VAR
VAR_OUTPUT
heater_command : INT;
END_VAR
VAR
seq : PasteurizerSequence;
pid : PID;
END_VAR
WIRING
seq(abort := abort);
pid(
measurement := temp,
setpoint := target,
enable := seq.heat_on
);
heater_command := pid.output;
END_WIRING
END_PROGRAM
Key points:
- The SSFC says what it wants (
heat_on), the composition root says how it is delivered (pid.enable). That split is what lets either side change without touching the other. - The
pidinstance runs in every scan.enable := seq.heat_oncontrols the PID's internal enable logic; it does not gate whetherpidexecutes. heater_command := pid.outputreads an output port directly. Wiring only coversVAR_INPUTports.- A state's activity marker
Heating.Xis readable, but prefer a named output.Xcouples the composition to the sequence's internal state names; an output is a promise the sequence makes, and renaming a state then costs nothing. - Identifiers are case-insensitive, so an output
heatingand a stateHeatingcollide. The compiler says so, at the declaration.
Common mistakes at this step:
- Putting
WIRINGinside aSTATE. There is no per-state wiring: the parser acceptsWIRINGat block level only. A child that must behave differently per state is wired once, to a value the states set. - Placing the child call in a state action (
EN/DU/EX) instead of inWIRING. Action blocks assign to the SSFC's own variables; they do not wire child instances.
Compose an SSFC inside an SSFC¶
Declare the child in VAR and wire its input ports in the parent's one
WIRING block — the same rule as for a FUNCTION_BLOCK. Elapsed time is
runtime-owned: the child SSFC tracks scan duration from the runtime clock
independently, and no timestep is forwarded.
A child that should only run in part of the sequence is told so through a value, not through where the wiring sits:
SSFC OuterSequence
VAR_INPUT
start : BOOL;
abort : BOOL;
END_VAR
VAR_OUTPUT
done : BOOL;
END_VAR
VAR
inner : InnerSequence;
END_VAR
WIRING
inner(
start := Running.X, (* active only while Running *)
abort := abort
);
END_WIRING
INITIAL STATE Idle
TRANSITION TO Running WHEN start;
END_STATE
STATE Running
DU: done := inner.done;
TRANSITION TO Idle WHEN inner.done;
END_STATE
END_SSFC
Star-wiring for same-name passthrough¶
To forward parent ports to a child without writing out each binding, use inst* (star-wiring). This is shorthand for wiring every VAR_INPUT port of the child that has a matching name and compatible type in the parent scope.
VAR
valve : ValveController;
END_VAR
WIRING
valve*; (* forwards temp, pressure, enable from parent to valve
if valve has VAR_INPUT ports named temp, pressure, enable *)
END_WIRING
Rules:
- Only ports where the name and type match exactly are wired by
*. A name match with a type mismatch is a static error. - Ports with a name mismatch are not wired — they are silently skipped by
*and must be wired explicitly alongside the star. inst*and explicit port bindings can appear in the sameWIRINGblock. Explicit bindings take precedence over any port*would have matched.
When to use star-wiring vs. explicit wiring:
Use inst* for boilerplate passthrough — parent and child were designed to match. Use explicit wiring when the mapping is non-trivial, the names differ, or the binding expression needs a transformation (e.g., enable := Heating.X).
Common mistakes at this step:
- Assuming
*wires output ports back to the parent. Star-wiring only coversVAR_INPUTon the child. - Using
*when the parent has renamed a port (e.g., parent hastemperature, child expectstemp). In that case the port is not matched and must be wired explicitly.
Diagnosing common errors¶
| Error | Cause | Fix |
|---|---|---|
| Unmatched port | A VAR_INPUT port of the child is not wired at the call site or WIRING block |
Wire the missing port explicitly |
| Type mismatch | A wired expression has the wrong type for the port | Check the port type in the child's VAR_INPUT declaration |
Missing WIRING in some states |
The same child instance is wired in one state's WIRING block but absent from another reachable state |
Add a WIRING block to the other states, or move the instance to a top-level WIRING block if the binding is state-independent |
| Expression in output forwarding | Output forwarding RHS is not a simple reference | Move the computation to IMPLEMENTATION and forward the derived variable |
PARAMETER wired at call site |
A PARAMETER port appears in the WIRING block or FB call |
Move the binding to the VAR declaration: inst : ChildFB(Kp := 2.0) |
See also¶
- Why FB and SSFC are equal-rank components:
concepts/component-model.md - Normative WIRING specification:
reference/language.md§10 - PROGRAM root POU:
reference/language.md§3.6 - Normative WIRING/IMPLEMENTATION separation:
docs/adr-pg12-wiring-composition.md