How to Model Concurrent Sequences with Parallel Regions¶
Goal: Model a controller where two or more independent sequences run simultaneously and must synchronise at a barrier before continuing.\ Requires: How-to: Model a Sequential Machine with SSFC — understand single-region SSFCs first.
When to use parallel regions¶
Use PARALLEL / REGION / JOIN when:
- Two tasks must happen concurrently (order between them does not matter, but both must complete before the controller can proceed).
- Each task has its own state machine (its own INITIAL state, transitions, lifecycle blocks, and independent termination condition).
- A single abort or preemption should terminate all concurrent tasks at once.
Do not use parallel regions to model a round-robin or mutually exclusive choice — those are exclusive regions within a single sequential chain.
The syntax¶
SUPERSTATE ConcurrentWork
PARALLEL
REGION regionA
INITIAL STATE TaskA
-- TaskA lifecycle blocks and transitions
END_STATE
STATE DoneA END_STATE
END_REGION
REGION regionB
INITIAL STATE TaskB
-- TaskB lifecycle blocks and transitions
END_STATE
STATE DoneB END_STATE
END_REGION
JOIN FROM DoneA, DoneB TO NextState;
END_PARALLEL
TRANSITION TO Aborted WHEN emergency; -- preempts ALL regions
END_SUPERSTATE
STATE NextState
-- entered only when both DoneA AND DoneB are simultaneously active
END_STATE
What happens scan by scan:
- When
ConcurrentWorkis entered (FORK), bothTaskAandTaskBare activated simultaneously in the same scan. - Each region runs its own transitions independently. In any given scan, TaskA may advance to DoneA while TaskB stays in TaskB (or vice versa).
- The JOIN fires in the first scan where both
DoneAandDoneBare simultaneously active and the JOIN's guard (default: TRUE) is met. It exits both regions atomically in the same scan and entersNextState. - If
emergencygoes HIGH at any point whileConcurrentWorkis active, the preemption fires: all active region leaves (TaskA or DoneA; TaskB or DoneB) run their EX blocks,ConcurrentWorkexits, andAbortedis entered.
A worked example: two-arm robot calibration¶
A robot arm has two joints (A and B) that must be calibrated independently before the arm can move:
SSFC ArmCalibration
VAR_INPUT
start : BOOL;
a_at_home : BOOL; a_cal_done : BOOL;
b_at_home : BOOL; b_cal_done : BOOL;
stop : BOOL;
END_VAR
VAR_OUTPUT
joint_a_cmd : BOOL; -- move joint A
joint_b_cmd : BOOL; -- move joint B
ready : BOOL; -- arm ready to operate
END_VAR
INITIAL STATE Idle
TRANSITION TO Calibrate WHEN start;
END_STATE
SUPERSTATE Calibrate
PARALLEL
REGION arm_a
INITIAL STATE A_Home
EN: joint_a_cmd := TRUE;
EX: joint_a_cmd := FALSE;
TRANSITION TO A_Cal WHEN a_at_home;
END_STATE
STATE A_Cal
EN: joint_a_cmd := TRUE;
EX: joint_a_cmd := FALSE;
TRANSITION TO A_Done WHEN a_cal_done;
END_STATE
STATE A_Done END_STATE
END_REGION
REGION arm_b
INITIAL STATE B_Home
EN: joint_b_cmd := TRUE;
EX: joint_b_cmd := FALSE;
TRANSITION TO B_Cal WHEN b_at_home;
END_STATE
STATE B_Cal
EN: joint_b_cmd := TRUE;
EX: joint_b_cmd := FALSE;
TRANSITION TO B_Done WHEN b_cal_done;
END_STATE
STATE B_Done END_STATE
END_REGION
JOIN FROM A_Done, B_Done TO Calibrated;
END_PARALLEL
TRANSITION TO Idle WHEN stop;
END_SUPERSTATE
STATE Calibrated
EN: ready := TRUE;
TRANSITION TO Idle WHEN NOT start;
END_STATE
END_SSFC
- While
Calibrateis active,arm_aandarm_brun independently. A_Home may advance to A_Cal while B_Home has not yet reached b_at_home. - Once both A_Done and B_Done are active, the JOIN fires in that scan.
stopaborts calibration from any sub-state in either region.
Write-conflict rule¶
Two simultaneously-active regions must not write the same output variable in lifecycle blocks. If they do, the compiler rejects it:
-- ERROR: ssfc-cross-region-conflict
REGION arm_a
INITIAL STATE A_Work
DU: status_light := TRUE; -- arm_a writes status_light
END_STATE
END_REGION
REGION arm_b
INITIAL STATE B_Work
DU: status_light := FALSE; -- arm_b ALSO writes status_light → conflict
END_STATE
END_REGION
Fix options:
- Use separate output variables per region (
light_a,light_b). - Compute the combined value in the superstate's DU block (which runs while any
child region is active):
SUPERSTATE ConcurrentWork DU: status_light := A_Done.X OR B_Done.X; -- shared derived value PARALLEL ... END_PARALLEL END_SUPERSTATE - Use a FUNCTION_BLOCK composed with the SSFC to merge the outputs.
Priority between JOIN and preemption¶
When both a JOIN guard and a parent-level preemption guard are simultaneously true, the preemption wins. Preemption transitions always have higher implicit priority than child transitions (including JOIN) in the same scan.
If you need the JOIN to win over a preemption under some conditions, you can encode this explicitly using PRIORITY — but usually the correct design is to let the preemption win (that is its purpose).
JOIN with a guard¶
A JOIN may declare an optional WHEN guard:
JOIN FROM TaskA, TaskB TO NextState WHEN all_checks_ok;
The JOIN fires only when all of TaskA, TaskB are active and
all_checks_ok is TRUE. If all_checks_ok is FALSE the machine waits in the
current marking (both sources active) until the guard becomes TRUE.
A JOIN guard that is never satisfiable is flagged by C-DATA-DEAD.
What the assurance report says about parallel regions¶
After python -m lola arm_cal.lola, the report includes:
- C-SAF: Token balance is checked per (region, transition) pair including the FORK (entering Calibrate activates initial states in both regions) and the JOIN (exiting A_Done and B_Done simultaneously).
- C-INT: Both arm_a and arm_b are covered by the Calibrate superstate's preemption transition. The JOIN sources (A_Done, B_Done) are in distinct regions.
- C-SYNC-DEAD (part of C-INT): Both A_Done and B_Done are reachable by at least one transition, so neither is a structural orphan. This does not prove they can be simultaneously active — it only checks that neither is trapped with zero inbound transitions.
- C-REACH: A_Done and B_Done are each individually reachable from the initial marking. C-REACH does not prove co-reachability (that both can be active at the same time).
- C-HOME: Over-approximation applies most visibly in parallel regions. A state in arm_a may receive a C-HOME PASS via a witness path that exits Calibrate via the preemption — this path is valid for arm_a's state alone, but whether arm_b is simultaneously in a position to take that path is not verified.
For parallel SSFCs, the structural claims (C-SAF, C-INT) are exact; the reachability and home claims are over-approximations.