Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutRoadmapCareersInternshipsFAQContactPrivacyTerms
Skip to lesson

Course contents

  • How to Use This Course
  • Reading Paths
  • Glossary
  • Metrics Reference
  • Formal Cheat Sheet
  • Formal Foundations19
  • Properties and SVA16
  • Property Development and Constraints19
  • Proof Engines and Convergence23
    • Model Checking in Plain Terms
      • Transition Systems and Reachability
      • BDDs and SAT in Two Pages
      • Safety as Reachability and the Engine Menu
      • Why Results Differ Between Runs
    • Bounded and Unbounded Results
    • K-Induction in Depth
    • IC3, PDR and Interpolation, Concept Level
    • Fairness, Lassos and Liveness-to-Safety
    • Proof Convergence Tactics
  • Formal Applications20
  • Equivalence Checking14
  • Formal Coverage and Signoff15
  • Methodology and Debug13
  • Interview Prep15
  • Capstone: A Chip-Block Verification Lab24
Loading lesson

←Model Checking in Plain Terms
  1. Topics
  2. Formal Verification
  3. Proof Engines and Convergence
  4. Model Checking in Plain Terms
  5. Transition Systems and Reachability
On this page

    Part 4 of 10, lesson 1 of 23Part 4 of 10/Proof Engines and Convergence/Lesson 1 of 23All levels

    Transition Systems and Reachability

    A design as states plus steps, the reachable set as a fixed point, and safety as the absence of a bad reachable state, computed on the decade counter.

    Why it matters

    A reviewer asks the question that decides whether a proof means anything: which states can this design actually reach? A property that holds on every reachable state is safe even if some unreachable state would break it. A property that breaks only in an unreachable state still fails induction, as the k-induction topic shows. Everything starts from the same picture: states, steps, and the set of states reachable from reset.

    The core idea

    A synchronous design is a transition system. A state is the value of every register: the decade counter has 4 state bits, so 16 states, of which 10 (0 to 9) are reachable from reset and 6 (10 to 15) are not. The transition relation says which next states each state can step to under some input: with enable high the counter advances, with enable low it holds.

    The reachable set is the smallest set that contains the reset state and is closed under steps: start from reset, add every successor, repeat until nothing new appears. That repetition is a fixed point, and it always terminates because there are only finitely many states. Safety is then one sentence: no bad state is in the reachable set. A bad state here is count 15, and the property count never 15 is the claim that 15 stays outside the reachable set. Liveness, covered later, is a claim about infinite paths, not about membership: it needs loops, called lassos, under fairness, and it cannot be reduced to one bad state.

    Worked example: forward and backward on the counter

    The script below computes both directions on the 16 counter states with enable free. Forward from reset reaches exactly 0 to 9. Backward from the bad state 15 collects the states that can reach it: 10 to 15, none of them reachable from reset, which is why the property holds and also why induction struggles with it later. The injected variant models reset wrongly, with the start state left free: now 15 is reachable from the start, and the same check catches it.

    example.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    def succ(s, last=9):
        outs = {s}  # enable low holds the count
        outs.add(0 if s == last else (s + 1) % 16)  # enable high advances
        return outs
    
    def forward(init, last=9):
        seen = set(init)
        frontier = list(init)
        while frontier:
            s = frontier.pop()
            for t in succ(s, last):
                if t not in seen:
                    seen.add(t)
                    frontier.append(t)
        return seen
    
    def backward(bad, last=9):
        can = set(bad)
        changed = True
        while changed:
            changed = False
            for s in range(16):
                if s not in can and any(t in can for t in succ(s, last)):
                    can.add(s)
                    changed = True
        return can
    
    good = forward({0})
    assert good == set(range(10)), sorted(good)
    assert 15 not in good
    back = backward({15})
    assert 0 not in back, "bad state reachable from reset"
    
    # injected variant: start state left free instead of reset to 0
    free = forward(set(range(16)))
    caught = False
    try:
        assert 15 not in free  # this must fail: a free start reaches 15
    except AssertionError:
        caught = True
    assert caught, "checker did not catch the free-start model"
    print("PASS: reachable set is 0..9, 15 outside it; free-start model reaches 15")
    Flowchart looping from the reset state through adding successors until nothing new appears, ending at the fixed point, the reachable set.
    The reachability fixed point: grow from reset until no new state appears.

    What the review looks for

    • The lesson names the state bits and the reset state before any engine is mentioned.

    • Reachable is distinguished from existing: 10 of 16 counter states are reachable, and the property is argued on the 10.

    • Every later claim about induction or abstraction refers back to this reachable set, not to intuition.

    Verify and debug

    Recheck the reachable count against the covers the tool reports: the deepest first-reach step for count 9 is step 10, and the wrap cover needs step 14, so a bounded run shorter than depth 11 never sees the terminal pulse. If a counterexample starts in a state the enumeration above cannot reach, the reset model is wrong, not the design: the harness must hold reset in the first step and release it after, which the design files do with the init flag.

    A counterexample that starts in an impossible state: the run left the start state free instead of modelling one reset sequence, so the first trace begins with register values no reset can produce. The symptom is a failure the designers cannot replay from reset. The fix is one reset step at time zero, then release, with the first assertion guarded past it. It is found by checking that every counterexample begins at reset, which the backward computation above makes mechanical.

    If asked what model checking is, answer in three sentences: a design is states plus steps, the reachable set is the fixed point from reset, and safety means no bad state is reachable. Then offer the numbers: 16 states, 10 reachable here, and the property is a membership claim about that set.

    Check yourself The backward computation from 15 returns 10 to 15. Why does that not contradict the proof that 15 is unreachable?

    Backward collects states that could reach 15, including unreachable ones like 14. Unreachable states leading to a bad state do not threaten safety; they threaten induction, because the induction step starts from arbitrary states. That is the next topic's problem.

    Related topics

    • Next: BDDs and SAT in Two Pages

    • Formal: Transition Systems and Properties

    • First Principles: Bounded Proofs

    • Protocol First Principles: Safety vs Liveness

    Previous

    60 / 183

    Next: BDDs and SAT in Two Pages

    Up next

    Up nextBDDs and SAT in Two Pages

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Safety as Reachability and the Engine Menu
    • Why Results Differ Between Runs