Everything in this module so far —
The design is a state machine: its flip-flops define a state, and each clock edge moves it to a
successor state that depends on the inputs. A model checker treats this as a graph
problem. Start at the reset state; follow every possible input, to every successor, and theirs, and
theirs — until no new state appears. That fixed set is the reachable states, and
the property (the very same SVA you wrote in the
Note the word reachable. A design with
The catch has a name: state-space explosion. Every extra flip-flop
doubles the state space —
Around 55 flip-flops, the states outnumber what a year of brute-force simulation could even touch — and a single 64-bit datapath register blows past that on its own. This is why formal tools do not enumerate states one by one (they reason about huge sets of states symbolically), and it is why formal has a home turf: control logic. Arbiters, FIFO pointers, handshake protocols, state machines — tens of flip-flops of dense, corner-case-riddled logic where bugs love to hide and the reachable space is small enough to conquer. A 64-bit multiplier's datapath, by contrast, is simulation's job (or specialised equivalence tricks). The craft of formal is carving out the control kernel and proving it watertight.
When a full proof is out of reach, tools fall back to bounded model checking
(BMC): prove the property holds for every input sequence of length up to
For a small design you can do exhaustive reachability by hand — breadth-first search over states, trying every input at every step. Here is a two-requester round-robin arbiter (3 flops: two grants plus a fairness bit) and the property no two grants ever simultaneous — first a correct design, then one whose arbitration was "simplified" away:
Two things to savour. The proof covered only 4 reachable states, not all 8 — reachability did real work. And the failure comes back not as a percentage or a red bar but as a counterexample: the exact, minimal input sequence — reset, then both request — driving the design into the bad state. Real formal tools hand you this as a waveform; debugging a formal counterexample is often easier than debugging a random-simulation failure, because the tool has already minimised the story for you. The counterexample is the payoff.
Equivalence checking. After synthesis turns your RTL into a gate-level netlist, how does anyone know the tool preserved the design's meaning? Not by re-running the regression on gates (weeks of runtime) — by formally proving that RTL and netlist compute identical next-state and output functions, register by register. Every tape-out on Earth rests on this proof; it is why the industry gets to trust synthesis and never simulate the netlist exhaustively.
Assume–guarantee at interfaces. A block in isolation sees inputs its real neighbours could never send, so naive formal drowns in false alarms. The fix: assume properties about the inputs (legal protocol only), and prove your properties under those assumptions. The elegant part — when the neighbouring block is verified, your assumptions become its proof obligations (its guarantees), and the proofs compose across the interface like a contract with matching signatures. Divide, constrain, and conquer.
It cannot — one at a time. The 1980s breakthrough (Clarke, Emerson and Sifakis, later the 2007 Turing Award) was symbolic model checking: represent an astronomically large set of states as a compact formula — first as binary decision diagrams, later as SAT problems — and step whole sets through the design's logic at once. "All states where the FIFO is empty and no grant is high" might be a trillion states and a twelve-node data structure. The state count stops mattering; the structure of the logic is what the tool pays for. That is also the honest reading of formal's limits: on tangly control logic the formulas stay small and proofs fly; on multipliers they balloon, which is why your formal testplan says "arbiter: prove" and "datapath: simulate" — and why the modern SAT-solver renaissance keeps moving that boundary outward.
Formal's guarantee is airtight about exactly one thing: the listed properties hold in every
reachable state. Weak properties, weak guarantee: prove three easy assertions about a cache
controller and the marketing sentence "formally verified" is true while the eviction logic ships
broken. Worse is the vacuous proof: req |-> ##2 grant is
triumphantly "proven" on a design where an over-tight input assumption means req can
never rise — the implication held because it was never tested. (An assumption contradiction can
even prove false.) Good tools report vacuity and covered preconditions; good engineers
read those reports, and ask the same question this module keeps asking of coverage and
scoreboards alike: not "did the check pass?" but "could this check have failed?" Formal
moves the risk from "did we try the scenario?" to "did we state the requirement?" — the burden of
imagination never goes away, it just changes address.