Reachability and Model Checking
Model checking is algorithmic formal verification. Given a finite-state model and a formal property, it attempts to answer whether the model satisfies the property. In embedded systems, this is useful for checking mode logic, protocols, mutual exclusion, controller supervision, and abstractions of software or hardware.
Lee and Seshia build model checking around reachability. If a bad state is unreachable, then a safety invariant holds. If an accepting cycle corresponding to the negation of a liveness property is reachable, then the model has a counterexample. The power of the method is exhaustive exploration; the challenge is state explosion.
Definitions
An open system receives inputs from and may produce outputs to an environment. A closed system has no inputs. Verification usually closes the system by composing the design with an environment model.
The reachable set is the set of states that can occur from the initial state under the transition relation.
Reachability analysis computes or overapproximates this set.
A property of the form
holds if proposition is true in every reachable state of the closed system.
Explicit-state model checking stores and explores individual states, often using depth-first search (DFS) or breadth-first search (BFS).
Symbolic model checking represents sets of states by logical formulas or specialized data structures such as binary decision diagrams, and manipulates sets rather than individual states.
The state-explosion problem is the exponential growth of composite state spaces. If components have states, the product can have
states.
An abstraction hides details to reduce state space. A sound abstraction may have more behaviors than the concrete system, so if the abstraction satisfies a universal safety property, the concrete system does too.
CEGAR stands for counterexample-guided abstraction refinement. It begins with a coarse abstraction, checks it, and refines it when a counterexample is spurious.
Key results
To verify , compute all reachable states and check on each one. If any reachable state violates , the path to that state is a counterexample. If no reachable state violates , the invariant holds.
Explicit DFS is linear in the size of the explored state graph, but the graph may be enormous relative to the model description. This makes representation and reduction techniques central.
Symbolic search computes a fixed point over sets:
where is the set of one-step successors of states in . When , the reachable set has been found.
Abstraction can replace large data domains with predicates. If a proof only needs whether old == new, a Boolean predicate may replace two 32-bit variables, shrinking a potential value space to abstract values for that aspect.
Liveness model checking often converts the negation of an LTL property into an automaton and composes it with the system. If the product has a reachable accepting cycle, then a counterexample trace exists.
Nested DFS detects such cycles by first finding an accepting state reachable from the initial state, then searching from that accepting state back to itself.
Closed-system construction is a modeling step that deserves review. If the environment model is too permissive, verification may fail for unrealistic input sequences. If it is too restrictive, verification may pass by excluding real hazards. For example, a pedestrian model for a traffic light should allow button presses at inconvenient times if those are physically possible. A communication model should include message loss or delay if the real network can exhibit them.
Reachability also explains why simulation tests are not enough for safety-critical systems. Running a model on many sample inputs explores some paths, while reachability attempts to cover all paths in the finite abstraction. The price is computational cost, but the reward is a stronger statement: no reachable state violates the invariant, subject to the model assumptions.
Abstraction must preserve the property being checked. Hiding a timer variable may be fine for proving that green and pedestrian-walk are never simultaneous, but not fine for proving that yellow lasts at least five seconds. Hiding a data value may be fine for proving control-state reachability, but not for proving an arithmetic bound. The best abstraction is not the smallest one; it is the smallest one that is still sound for the property.
Counterexamples should be treated as design artifacts. A safety counterexample gives a finite path from the initial state to a bad state. That path can be translated into a test, a simulation scenario, or a requirements discussion. If the path depends on unrealistic environment behavior, the environment model should be refined and the assumption documented. If the path is realistic, it is a bug in the model or design.
For liveness properties, the counterexample often has a stem and a loop. The stem reaches a region of the state graph, and the loop repeats forever while avoiding the desired response. For example, a request-response property fails if there is a reachable cycle in which the request has occurred and the response never occurs. This shape explains why accepting-cycle detection is the core graph problem behind many LTL model checkers.
State explosion should be anticipated during modeling, not only after a tool runs out of memory. A model with five independent Boolean flags already has combinations before modes, counters, queues, and environment state are added. Bounded queues, timers, and data variables should use the smallest domains that preserve the property under study.
Model checking results should always be read with the model boundary in mind. A proof that the model satisfies is not a proof that the deployed system satisfies unless the model faithfully overapproximates the relevant implementation and environment behavior. This is why model checking pairs naturally with refinement: abstractions, environment assumptions, and generated code need traceable relationships.
Visual
| Technique | Represents | Best for | Limitation |
|---|---|---|---|
| Explicit DFS/BFS | Individual states | Counterexample paths, software-like models | Memory blowup |
| Symbolic search | Sets of states | Hardware/control with regular structure | Formulas can still blow up |
| Abstraction | Simplified model | Large systems with irrelevant details | Spurious counterexamples |
| CEGAR | Iteratively refined abstraction | Automated proof search | May require many refinements |
| Nested DFS | Reachable accepting cycles | LTL liveness checking | Explicit-state cost |
Worked example 1: Reachable safety check
Problem: A closed FSM has states , initial state , and transitions , , , and . Proposition is true in , , and , but false in . Does hold?
Method:
- Start reachable set:
- Add successors of :
- Add successors of and . reaches ; assume has no new successors:
-
Add successors again. reaches , already included, so fixed point is reached.
-
Check in every reachable state. and .
Answer: does not hold. The path is a counterexample.
Worked example 2: Product state explosion
Problem: A controller has states, a sensor abstraction has states, an actuator model has states, and an environment model has states. Compute the maximum composite state-space size under synchronous product composition.
Method:
- Product size is the multiplication of component sizes:
- Multiply in stages:
- Final product:
- Interpret. This is an upper bound; unreachable combinations may be fewer, but a model checker may need to reason about the product structure.
Answer: The composed model has up to states.
Code
def reachable(initial, transitions):
seen = {initial}
stack = [initial]
while stack:
state = stack.pop()
for nxt in transitions.get(state, []):
if nxt not in seen:
seen.add(nxt)
stack.append(nxt)
return seen
transitions = {"A": ["B", "D"], "B": ["C"], "C": ["B"], "D": []}
bad = {"D"}
states = reachable("A", transitions)
print("reachable:", states)
print("safe:", states.isdisjoint(bad))
Common pitfalls
- Verifying an open system without modeling its environment. Unconstrained inputs may create irrelevant counterexamples or hide assumptions.
- Confusing unreachable bad states with reachable failures. Safety depends on reachability, not mere existence in the diagram.
- Treating an abstraction proof backwards. If an overapproximation is safe, the concrete model is safe; if it is unsafe, the counterexample may be spurious.
- Forgetting that state variables multiply the state space.
- Checking only safety properties when the requirement is liveness or fairness.
- Assuming symbolic model checking always scales. Symbolic representations can also become exponential.