Skip to content

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:

  • PARAMETER values (Kp, Ki) are bound at declaration time in VAR, not in WIRING.
  • Every VAR_INPUT port 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 PARAMETER in WIRING instead of in the VAR declaration — PARAMETER is not a wiring target.
  • Placing computed logic (IF … THEN …, arithmetic) in WIRING — move it to IMPLEMENTATION.
  • 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.X is the BOOL activity marker for the Heating state — TRUE while Heating is the active state, FALSE otherwise.
  • The pid instance runs in every scan. Wiring enable := Heating.X controls the PID's internal enable logic; it does not gate whether pid itself executes.
  • heater_command := pid.output in the DU block is a direct read of an output port, not a wiring expression. Wiring only covers VAR_INPUT ports.
  • If the same child instance appears in multiple states' WIRING blocks, 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 in WIRING. Action blocks are for assignments to the SSFC's own variables, not for wiring child instances.
  • Wiring enable := FALSE in one state but forgetting the WIRING block 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_dt port 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_INPUT ports. 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 same WIRING block. 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 covers VAR_INPUT on the child.
  • Using * when the parent has renamed a port (e.g., parent has temperature, child expects temp). 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