Extracts an SMV (NuSMV/nuXmv) finite-state model from code or state-machine descriptions, for CTL/LTL model checking of reactive systems. Use when verifying hardware-adjacent or embedded logic, when the state space is naturally finite and small, or when CTL branching-time properties are needed.
71
86%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
SMV is for small, finite, synchronous systems — think hardware controllers, protocol handshakes, embedded state machines. Compared to → program-to-tlaplus-spec-generator: SMV is synchronous (all transitions fire together) and finite by construction; TLA+ is asynchronous and handles unbounded state.
| Characteristic | SMV fits | TLA+ fits |
|---|---|---|
| State space | Naturally finite (< 10⁸ states ideally) | Can be unbounded (abstracted) |
| Execution model | Synchronous — all modules step together | Asynchronous interleaving |
| Property language | CTL (branching) + LTL | TLA (linear, actions) |
| Source domain | Hardware, embedded, protocol handshakes | Distributed systems, concurrency |
MODULE main
VAR
state : {idle, waiting, active, error};
counter : 0..15;
input : boolean;
ASSIGN
init(state) := idle;
next(state) := case
state = idle & input : waiting;
state = waiting & counter = 0 : active;
state = active & !input : idle;
TRUE : state; -- stutter
esac;
init(counter) := 0;
next(counter) := case
state = waiting & counter > 0 : counter - 1;
TRUE : counter;
esac;
SPEC AG (state = error -> AF state = idle) -- CTL: always recoverable| Code pattern | SMV construct |
|---|---|
enum State { A, B, C } | state : {A, B, C} |
switch(state) { case A: ... } | next(state) := case state = A : ...; esac |
Bounded int (uint4_t, i % 16) | x : 0..15 |
| Bool flag | flag : boolean |
if (cond) state = X | One branch in the case |
| Parallel components | Separate MODULEs, instantiated in main |
| Shared variable between components | Passed as parameter: MODULE foo(shared_var) |
Code (traffic light controller):
enum { RED, GREEN, YELLOW } light = RED;
int timer = 0;
void tick(bool car_waiting) {
switch (light) {
case RED:
if (timer >= 30 && car_waiting) { light = GREEN; timer = 0; }
else timer++;
break;
case GREEN:
if (timer >= 25) { light = YELLOW; timer = 0; }
else timer++;
break;
case YELLOW:
if (timer >= 5) { light = RED; timer = 0; }
else timer++;
break;
}
}SMV:
MODULE main
VAR
light : {RED, GREEN, YELLOW};
timer : 0..31;
car_waiting : boolean; -- environment input, unconstrained
ASSIGN
init(light) := RED;
init(timer) := 0;
next(light) := case
light = RED & timer >= 30 & car_waiting : GREEN;
light = GREEN & timer >= 25 : YELLOW;
light = YELLOW & timer >= 5 : RED;
TRUE : light;
esac;
next(timer) := case
next(light) != light : 0; -- reset on transition
timer < 31 : timer + 1;
TRUE : timer; -- saturate
esac;
-- Safety: never GREEN and YELLOW simultaneously (trivially true here — single var)
-- Liveness: if a car waits, eventually GREEN
LTLSPEC G (car_waiting -> F light = GREEN)timer capped at 31 — SMV needs finite ranges. The saturation at 31 is a modeling choice; check it doesn't affect the property (here it doesn't — thresholds are all ≤ 30).
Check: LTLSPEC fails if car_waiting can oscillate — a car that waits then leaves then waits again resets no clock. The fix is in the property (G car_waiting -> F GREEN — persistent waiting) or a fairness assumption.
SMV requires finite domains. When the code has unbounded state:
| Unbounded construct | Bounding strategy |
|---|---|
| Counter that grows forever | Cap at max(threshold) + 1 — values above the highest comparison are equivalent |
| Message queue | Bound length. If property violated → was it at the bound? Raise the bound. |
| Arbitrary data payload | Abstract to a few symbolic values. data : {d1, d2} — if it doesn't affect control, one value suffices. |
If you can't sensibly bound it, SMV is the wrong tool — use TLA+ with a state constraint, or go parametric.
TRUE : state stutter branch. Without it, case is partial → "incomplete case" or arbitrary next value.car_waiting : boolean with no constraint means it can toggle every tick — that's maximum chaos, often too adversarial.0..1000000 → NuSMV allocates log₂(10⁶) ≈ 20 bits per state. Blowup is exponential in bits.## Finite-state abstraction
<var> : <range> — <what code construct, how bounded>
## SMV model
<code block>
## Properties
<CTL/LTL specs — each traced to a requirement>
## Bounding assumptions
<every cap you imposed, and why it's sound for this property>47d56bb
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.