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 (the per-state WIRING block)¶
To host a child FUNCTION_BLOCK inside an SSFC, use the WIRING block inside each state that exercises the child. The WIRING block is declarative: the binding applies in every scan regardless of which state is active. State activity controls the value wired to the child, not whether the child runs.
SSFC PasteurizerSequence
VAR_INPUT
temp : MATHREAL;
target : MATHREAL;
abort : BOOL;
END_VAR
VAR_OUTPUT
heater_command : MATHREAL;
safe : BOOL;
END_VAR
VAR
pid : PID(Kp := 2.0, Ki := 0.1);
END_VAR
ASSUME abort;
INITIAL STATE Idle
TRANSITION TO Heating WHEN NOT abort;
END_STATE
STATE Heating
DU: heater_command := pid.output;
WIRING
pid(
measurement := temp,
setpoint := target,
enable := Heating.X (* active only while Heating *)
);
END_WIRING
TRANSITION TO SafeStopped WHEN abort;
END_STATE
STATE SafeStopped
EN: heater_command := 0.0;
DU: safe := TRUE;
WIRING
pid(
measurement := temp,
setpoint := target,
enable := FALSE (* PID disabled in SafeStopped *)
);
END_WIRING
END_STATE
END_SSFC
Key points:
Heating.Xis theBOOLactivity marker for theHeatingstate —TRUEwhileHeatingis the active state,FALSEotherwise.- The
pidinstance runs in every scan. Wiringenable := Heating.Xcontrols the PID's internal enable logic; it does not gate whetherpiditself executes. heater_command := pid.outputin theDUblock is a direct read of an output port, not a wiring expression. Wiring only coversVAR_INPUTports.- If the same child instance appears in multiple states'
WIRINGblocks, every such block must wire all of the child's input ports. Partial wiring in any state is a static error.
Common mistakes at this step:
- Placing the child call in a state action (
EN/DU/EX) instead of inWIRING. Action blocks are for assignments to the SSFC's own variables, not for wiring child instances. - Wiring
enable := FALSEin one state but forgetting theWIRINGblock entirely in another state that the sequence can reach. The compiler requires complete coverage for every reachable state that shares the same child instance.
Compose an SSFC inside an SSFC¶
To host a child SSFC inside a parent SSFC, declare the child in VAR and wire its input ports in the parent's per-state WIRING blocks. Elapsed time is runtime-owned — the child SSFC tracks scan duration from the runtime clock independently. No timestep forwarding is required.
SSFC OuterSequence
VAR_INPUT
start : BOOL;
abort : BOOL;
END_VAR
VAR_OUTPUT
done : BOOL;
END_VAR
VAR
inner : InnerSequence;
END_VAR
INITIAL STATE Idle
TRANSITION TO Running WHEN start;
END_STATE
STATE Running
WIRING
inner(
start := Running.X,
abort := abort
);
END_WIRING
DU: done := inner.done;
TRANSITION TO Idle WHEN inner.done;
END_STATE
END_SSFC
Key points:
- No
__ssfc_dtport exists. Elapsed time accumulation (T_elapsed,WITHIN, timer presets) is managed by each SSFC against the runtime clock. The parent does not forward a timestep. - Wire only the child's declared
VAR_INPUTports. Timing is not a wiring target.
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