Formal Model Checking (LTL & CTL)¶
fsmc includes a verification engine that checks statechart safety, liveness, and reachability properties against the state machine's transition graph (Kripke structure) prior to code generation.
Verification Principles¶
Model checking systematically explores reachable state configurations and possible transition sequences to evaluate formal specifications:
flowchart LR
Statechart["Statechart Model"] --> Kripke["State Transition Graph (Kripke Structure)"]
Properties["LTL / CTL Formulas"] --> Checker["Model Checking Engine"]
Kripke --> Checker
Checker --> Result["Verification Result (Sound / Counterexample Trace)"]
- Safety Properties: Verify that hazardous states or invalid state combinations are never reachable.
- Liveness Properties: Verify that designated recovery or terminal states are guaranteed to be reached following trigger events.
- Deadlock and Dead-State Detection: Identifies states with no legal outgoing transitions or unreachable branches.
Supported Temporal Logic Operators¶
The verification engine evaluates Linear Temporal Logic (LTL) and Computation Tree Logic (CTL) formulas natively over the state machine's transition graph without requiring external solvers.
Summary Reference Table¶
| Logic | Operator | Formula Syntax | Semantics & Description | Example Pattern |
|---|---|---|---|---|
| LTL | Globally (Always) | G (P) |
Property P must hold in every reachable state. |
G !(Armed && Charging) |
| LTL | Finally (Eventually) | F (P) |
Property P is guaranteed to become true at least once in future execution. |
F (SystemReady) |
| LTL | Response (Leadsto) | G (P -> F Q) |
Whenever stimulus P occurs, response Q is guaranteed to eventually follow. |
G (FaultDetected -> F SafeHold) |
| LTL | Next State | X (P) |
Property P must hold in the immediate next cycle step. |
G (DisarmCmd -> X Disarmed) |
| LTL | Until | P U Q |
Condition P remains true continuously until Q becomes true. |
Preheating U TargetTempReached |
| LTL | Recurrence | G F (P) |
Property P holds infinitely often across all cycles. |
G F (HeartbeatAck) |
| LTL | Persistence | F G (P) |
Eventually, property P holds permanently from some point onward. |
F G (InOrbit) |
| CTL | Exists Next | EX (P) |
There exists at least one immediate successor state satisfying P. |
EX (Calibrating) |
| CTL | All Next | AX (P) |
All immediate successor states satisfy P. |
AX (Preflight) |
| CTL | Exists Finally (Reachability) | EF (P) |
There exists at least one reachable path leading to a state satisfying P. |
EF (EmergencyStop) |
| CTL | All Finally (Inevitability) | AF (P) |
On all possible execution paths, P is inevitably reached. |
AF (Standby) |
| CTL | Exists Globally | EG (P) |
There exists an infinite execution path where P holds permanently. |
EG (NominalOperation) |
| CTL | All Globally (Universal Invariant) | AG (P) |
In all reachable states across all paths, P holds unconditionally. |
AG (!(Overheat && HighPower)) |
| CTL | Exists Until | E[P U Q] |
There exists at least one path where P holds until Q is reached. |
E[Charging U Full] |
| CTL | All Until | A[P U Q] |
On all possible branching paths, P holds until Q is reached. |
A[Precheck U Drive] |
Computation Tree Logic (CTL) Engine & Branching Semantics¶
While Linear Temporal Logic (LTL) reasons about single infinite linear execution traces, Computation Tree Logic (CTL) explicitly quantifies over the branching tree of non-deterministic execution paths:
graph TD
S0["S0: Standby"] --> S1["S1: Precheck"]
S0 --> S2["S2: Abort"]
S1 --> S3["S3: Drive"]
S1 --> S4["S4: EmergencyShutdown"]
S3 --> S3
S4 --> S4
classDef nominal fill:#1b4d3e,stroke:#2ecc71,stroke-width:2px,color:#fff;
classDef fault fill:#4d1b1b,stroke:#e74c3c,stroke-width:2px,color:#fff;
class S0,S1,S3 nominal;
class S2,S4 fault;
Path Quantifiers vs State Operators¶
Every temporal operator in CTL is paired with an explicit path quantifier:
- E (Existential Path Quantifier): "There exists at least one execution path starting from this state where..."
- Example: EF (Standby) verifies that from any point, it is possible to return to Standby (Reversibility).
- A (Universal Path Quantifier): "On every possible execution path starting from this state..."
- Example: AG (Fault -> AF Recovery) verifies that if a fault occurs, recovery is unconditionally inevitable on all branches.
Native Polynomial Fixed-Point Evaluation¶
fsmc computes CTL satisfaction sets \(Sat(\Phi) \subseteq S\) natively via direct fixed-point iteration on the explicit Kripke structure:
Because the Kripke state space is finite, all fixed-point computations terminate in at most \(|S|\) iterations (\(O(|S| + |T|)\) polynomial complexity), guaranteeing instant compile-time verification without external solver binaries.
[!NOTE] Sound Interval Evaluation & Formula Negation: In EFSM models with numeric datapath variables, atomic relational predicates under negation (such as
!(x == 5)or!(altitude < 1000)) are evaluated via relational operator inversion (\(x \neq 5\) and \(\text{altitude} \ge 1000\)) rather than boolean set complement. This preserves sound three-valued logic (True, False, Unknown) over interval lattices, preventing false certifications on unconstrained or unbounded interval states.
How to Use Temporal Operators: Practical Recipes¶
1. G (P) — Safety Invariants ("Bad things never happen")¶
Use G (Globally) when a condition must always remain true throughout the entire lifecycle of the state machine.
- Mutual Exclusion: Proving two orthogonal or conflicting states never activate at the same time:
- Hazard Prevention: Ensuring actuators are disabled during sensitive operations:
2. F (P) — Eventual Completion ("Good things eventually happen")¶
Use F (Finally) to prove that the state machine does not get stuck in a bootloader or infinite initialization trap and eventually reaches operational status.
- Initialization Guarantee:
3. G (P -> F Q) — Response / Leadsto (The Core System Pattern)¶
This is the most frequently used pattern in reactive systems. It proves that every time an event, trigger, or fault P occurs, the state machine is guaranteed to eventually transition to the handling state Q.
- Fail-Safe Response: Low battery must always lead to a safe landing state:
- Command Acknowledgment: When a reset command is received, the system must eventually return to Standby:
4. X (P) — Immediate Step Transition¶
Use X (Next) to verify the behavior of the single cycle immediately following an event.
- Immediate Interlock: Triggering an emergency stop must disarm the system on the very next step:
5. P U Q — Holding Conditions (Until)¶
Use U (Until) when a state machine must remain in a holding mode P up until a specific completion threshold Q is met:
- Solved via backward fixed-point computation: \(P\) must hold along all prefixes leading to a reachable state satisfying \(Q\).
- Any non-terminating cycle avoiding \(Q\) or violating \(P\) produces an explicit counterexample trace.
6. G F (P) — Recurrence ("Infinitely Often")¶
Use G F to prove liveness cycles and non-starvation:
- Evaluated via Tarjan's Strongly Connected Components (SCCs) decomposition: every reachable terminal cycle must contain at least one state satisfying \(P\).
7. F G (P) — Persistence ("Eventually Always")¶
Use F G to prove stabilization and convergence:
- Evaluated via SCC analysis: the statechart eventually transitions into a terminal cycle where \(P\) holds permanently in all states of the component.
EFSM Data-Path Abstract Interpretation Integration¶
ModelChecker integrates data-path interval abstract interpretation directly into property evaluation:
- Relational variable predicates (\(x > 10\), \(temp \le 100.0\)) evaluate against the fixed-point numeric intervals computed for each reachable state.
- If an upper bound \(iv.hi \le 100\) holds in every reachable state, \(G (temp \le 100)\) passes unconditionally without state explosion.
- Counterexamples pinpoint the exact state and transition where the variable interval strays outside the property bound.
Combining Logic Operators¶
You can compose complex multi-condition specifications using standard boolean operators:
- && (Logical AND)
- || (Logical OR)
- ! (Logical NOT)
- -> (Logical Implication: "if P then Q")
Example Multi-Condition Safety Property:
// If a critical sensor fault occurs while in HighSpeed mode, the machine must eventually enter EmergencyBrake
@fsm:property CriticalFaultBrake = "G ((SensorFault && HighSpeed) -> F EmergencyBrake)";
Mission-Critical Formal Safety Patterns¶
In safety-critical and high-integrity statecharts, formal specifications typically express canonical verification patterns:
| Safety Archetype | Informal Requirement | Canonical LTL Template | Concrete fsmc Example |
|---|---|---|---|
| Deadlock Freedom | The machine can never enter an unintended trap state without escape transitions. | G (!Deadlock) |
Built-in analysis (fsmc verify) |
| Hazard Invariant | Dangerous actuator states can never occur when safety interlocks are triggered. | G (HazardTrigger -> SafeState) |
G (DoorOpen -> !Spin) |
| Fault Recovery (Response) | Every detected fault condition must deterministically trigger safe degraded recovery. | G (Fault -> F RecoveryState) |
G (LowBattery -> F Landed) |
| Strict Ordering (Precedence) | A critical high-energy state can only be activated if preceded by complete self-test. | !ArmState U SelfTestPassed |
!Armed U CalibrationDone |
| Reversibility / Home Reachability | From any operational failure, the system can always return to a standby state. | G (F Standby) |
G (F Idle) |
Specifying Properties in Models¶
1. In OMG SysML v2¶
package FlightControl {
state def AircraftMission {
entry; then Preflight;
state Preflight;
state InFlight;
state Landed;
// Formal Safety Property: Mutual exclusion between Preflight and InFlight
@fsm:property DisjointFlight = "G (!(Preflight && InFlight))";
// Formal Liveness Property: Low battery always eventually leads to Landed
@fsm:property SafeLandingOnLowBattery = "G (LowBattery -> F Landed)";
}
}
2. In PlantUML / Mermaid¶
@startuml
[*] --> Preflight
state Preflight
state InFlight
state Landed
Preflight --> InFlight : TakeoffCmd
InFlight --> Landed : LowBattery
' Embedded Formal Verification Invariant
/' @fsm:property SafeDescent = "G (LowBattery -> F Landed)" '/
@enduml
Running Verification via CLI¶
Execute the model checker against your model:
Passing Output¶
[INFO] Parsed 8 states, 12 transitions from flight_mission.sysml
[INFO] Model Checking started:
[PASS] Property 'DisjointFlight': G (!(Preflight && InFlight))
[PASS] Property 'SafeLandingOnLowBattery': G (LowBattery -> F Landed)
[PASS] Deadlock Freedom: No terminal unhandled dead-end states detected
[PASS] Unreachable State Analysis: 0 dead states found
[SUCCESS] All 4 formal verification properties SOUND and VERIFIED.
Diagnosing Counterexample Violations¶
When a formal property fails, fsmc does not simply report a failure—it outputs a step-by-step counterexample trace pinpointing the exact transition sequence and data values that trigger the breach:
Case A: Safety Invariant Violation (Finite Trace)¶
A safety violation demonstrates a finite sequence of valid transitions leading to a forbidden state configuration:
package SystemSafety {
state def PowerUnit {
state Disconnected;
state Charging;
state HighLoad;
// Invariant: System must never be HighLoad while Charging
@fsm:property SafeOperation = "G !(Charging && HighLoad)";
entry; then Disconnected;
// Flaw: Unconditional transition into HighLoad while in parallel Charging region
transition plug_in first Disconnected then Charging;
transition start_load first Charging then HighLoad; // Bug: Transitions to HighLoad without disconnecting
}
}
error[E0401]: Formal safety property 'SafeOperation' violated!
--> power_unit.sysml:8:9
|
8 | @fsm:property SafeOperation = "G !(Charging && HighLoad)";
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Invariant breached
|
Counterexample Execution Trace:
[Step 1] Initial state: Disconnected
[Step 2] Event injected: EvPlugIn
[Step 3] Transition fired: Disconnected -> Charging
[Step 4] Event injected: EvStartLoad
[Step 5] Transition fired: Charging -> HighLoad (while charging active)
[Result] State configuration violates invariant 'G !(Charging && HighLoad)'!
package SystemSafety {
state def PowerUnit {
state Disconnected;
state Charging;
state HighLoad;
@fsm:property SafeOperation = "G !(Charging && HighLoad)";
entry; then Disconnected;
transition plug_in first Disconnected then Charging;
// Fixed: must transition through Disconnected before enabling HighLoad
transition stop_and_load first Charging accept EvStartLoad then Disconnected;
transition enable_load first Disconnected accept EvEnableLoad then HighLoad;
}
}
Case B: Liveness / Response Violation (Infinite Lasso Cycle)¶
A liveness violation (G (P -> F Q)) occurs when the system can enter an infinite loop (a "lasso cycle") where the required response Q is starved and never reached:
package FlightSystem {
state def MissionController {
state InFlight;
state HoldingPattern;
state Landed;
// Liveness: Low battery must always eventually lead to Landed
@fsm:property SafeLanding = "G (LowBattery -> F Landed)";
transition on_low_bat first InFlight accept LowBattery then HoldingPattern;
// Flaw: HoldingPattern loops indefinitely on EvHoldTick, with no path to Landed!
transition hold_loop first HoldingPattern accept EvHoldTick then HoldingPattern;
}
}
error[E0402]: Formal liveness property 'SafeLanding' violated!
--> mission.sysml:8:9
|
8 | @fsm:property SafeLanding = "G (LowBattery -> F Landed)";
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Liveness starved
|
Counterexample Lasso Execution Trace:
Prefix Path:
[Step 1] State: InFlight
[Step 2] Event: LowBattery
[Step 3] Transition fired: InFlight -> HoldingPattern
Infinite Cycle Loop (Lasso):
┌─► [Step 4] State: HoldingPattern
│ [Step 5] Event: EvHoldTick -> Transition fired: HoldingPattern -> HoldingPattern
└── (Cycle repeats indefinitely: 'Landed' is never reachable from this loop!)
package FlightSystem {
state def MissionController {
state InFlight;
state HoldingPattern;
state Landed;
@fsm:property SafeLanding = "G (LowBattery -> F Landed)";
transition on_low_bat first InFlight accept LowBattery then HoldingPattern;
// Fixed: HoldingPattern has guaranteed exit path to Landed
transition hold_loop first HoldingPattern accept EvHoldTick if in.holding_time < 5.0 then HoldingPattern;
transition descend first HoldingPattern if in.holding_time >= 5.0 then Landed;
}
}
CLI Formal Verification with fsmc verify¶
fsmc provides first-class, standalone model checking directly via the command line through the verify sub-command:
Options and Flags¶
| Option | Argument | Description | Default |
|---|---|---|---|
--engine |
auto, nuxmv |
Specifies the verification engine. auto runs the internal graph model checker. For nuXmv symbolic verification, export with -e smv. |
auto |
--ltl |
"<formula>" |
Injects an ad-hoc LTL formula for verification on the input model. | "" |
--ctl |
"<formula>" |
Injects an ad-hoc CTL formula for verification on the input model. | "" |
1. Verifying Structural Soundness and Built-in Invariants¶
Output:
============================================================================
Formal Model Verification Report: Connection
============================================================================
Input File: examples/connection_manager/connection.smv
States: 4
Total Events: 8
Transitions: 11
Choice Nodes: 0
Deferred Triggers:0
----------------------------------------------------------------------------
Diagnostics:
(No warnings or errors detected. Model is formally sound!)
----------------------------------------------------------------------------
Verification Status: PASSED (Model Sound & Properties Verified)
============================================================================
2. Ad-hoc LTL Verification with Execution Counterexample Trace¶
When verifying custom temporal properties from the CLI, any violation generates an exact state-by-state counterexample execution trace:
fsmc verify examples/connection_manager/connection.smv \
--ltl "G (State == Closed -> F State == Established)"
Output:
error[E_MODEL_CHECK_VIOLATION]: Formal property 'cli_ltl_property' [Safety] VIOLATED: Invariant 'G (State == Closed -> F State == Established)' evaluated to false in state 'Suspended'
Counterexample execution trace:
Step 0: State 'Disconnected' --[ConnectCmd if ((HasNetworkGuard && HasValidCredentialsGuard))]--> (Initial active state)
Step 1: State 'Connecting' --[HandshakeOkEvent]--> (Normal transition execution)
Step 2: State 'Connected' --[NetworkDegradedEvent]--> (Normal transition execution)
Step 3: State 'Suspended' (Invariant 'G (State == Closed -> F State == Established)' evaluated to false in state 'Suspended')
nuXmv / SMV Formal Logic Export¶
You can export the transition model directly into canonical SMV logic for external verification tools such as nuXmv: