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

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • How to Use This Course
  • Reading Path
  • Cache Coherency Glossary
  • The Shared Course Model
  • Cache Coherency Revision Sheet
  • Why Coherence Exists8
  • States and Protocols12
  • Transactions12
  • Races and Controller Design8
  • Progress and the Fabric16
  • Ordering and Synchronization12
  • Performance and Scalability12
  • Real Protocols12
  • SoC Integration12
  • Power, Clocking and Reliability12
  • Verification12
  • Debug and Bring-up12
    • Silicon Bring-up Coherency Triage
      • Trace-First Debug
      • Stale-Data Triage
      • Hang Triage
      • Symptom to Mechanism
    • Cross-Layer Observability Strategy
    • Post-Silicon Regression and Guardrails
  • Interview and Design Review17
  • Capstone: A Coherent Model Lab5
Loading lesson

‹Silicon Bring-up Coherency Triage
  1. Topics
  2. Cache Coherency
  3. Debug and Bring-up
  4. Silicon Bring-up Coherency Triage
  5. Hang Triage

Part 12 · Debug and Bring-up · All levels

Hang Triage

Dump pending entries and MSHRs, follow who waits for what, and separate a lost message from a resource cycle.

On this page

    A core stopped making progress: what is it waiting for?

    A watchdog fires: C0 has not retired a store for a long time. A timeout says only that something is slow. To classify the hang you need each controller's own record of what it waits for. The general method of following a wait backwards is in Following the Wait and the Hardware Debug course's hangs part. For coherence the records are fixed: the core-side MSHRs, H's pending entries, and the message queues between them.

    1. Take a snapshot: stop clocks and scan out, or freeze admission and read through the debug port, so the dump is one consistent moment (see Debug Hooks in Controllers).

    2. Dump every MSHR for the stuck line, H's pending entry for it, and the occupancy and credits of every queue on the path.

    3. Draw one edge per wait: waiter, target and the item it waits for (Data, Inv-Ack, a free slot, a credit).

    4. Follow the edges. At each target, check whether it holds a record of the item: an entry, a queued message or a resource it will release.

    5. Resume, wait, freeze and dump again. If counters moved, the system is not stuck: it is livelock, starvation or slow backpressure.

    Class

    Dump signature

    Taught in

    Lost message

    The chain ends at an agent with no record of the item: no queued message, no entry, no state change

    This lesson

    Deadlock

    The chain closes on itself; every waiter holds something the next one needs; two dumps are identical

    A Protocol Deadlock

    Livelock

    Retry, forward or Inv counters keep rising between dumps; no completions

    Deadlock, Livelock, Starvation

    Starvation

    Other requests complete; one entry's age keeps growing

    Starvation Cases

    Worked case A: H waits for an Inv-Ack that C1 never saw

    Warm-up: two completed reads, C0 S / 0, C1 S / 0, H S{C0,C1}, memory = 0, as in Upgrade from Shared, with the same roles: C0 upgrades, C1 is the sharer. In this illustrative failure the Inv to C1 is dropped on the way (a fabric or ingress bug: the course model promises reliable delivery, so a lost message is always a bug).

    C0 sends GetM to H, H sends an Inv to C1 that is never delivered, H waits for an acknowledgement and C0 waits for Data.
    A lost Inv: H correctly waits for the acknowledgement, so the system hangs instead of returning stale data.

    Step

    C0 state / value

    C1 state / value

    H directory

    Memory

    Newest data

    Checkpoint

    S / 0

    S / 0

    S{C0,C1}

    0

    C0, C1 and memory: 0

    L. C0 store stalls, GetM sent

    SM^D / 0

    S / 0

    S{C0,C1}

    0

    C0, C1 and memory: 0

    1. GetM at H

    SM^D / 0

    S / 0

    S{C0,C1}, pend GetM C0, acks 0/1

    0

    C0, C1 and memory: 0

    2. Inv sent, never delivered (bug)

    SM^D / 0

    S / 0

    S{C0,C1}, pend GetM C0, acks 0/1

    0

    C0, C1 and memory: 0

    Watchdog fires, system frozen

    SM^D / 0

    S / 0

    S{C0,C1}, pend GetM C0, acks 0/1

    0

    C0, C1 and memory: 0

    Agent

    Record in the dump

    Edge

    C0 MSHR

    Line X, SM^D, txn A, waiting for Data, stalled store X=1

    C0 waits for Data A from H

    H pending entry

    Line X, pend GetM C0, txn A, acks 0/1, Inv sent to C1

    H waits for Inv-Ack A from C1

    C1

    Line X in S / 0, no MSHR, no Inv in its input queue, no Inv-Ack in its output queue

    No record of txn A

    Fabric

    Forward and response queues between H and C1 empty, all credits returned

    Nothing in flight

    The chain C0 to H to C1 ends at an agent with no record: a lost message. C1's line state says which leg was lost. C1 still in S means the Inv never took effect at C1. C1's own port tap or ingress counter separates a fabric drop from C1 discarding the Inv. If C1 were I / - with nothing queued, check H's input for a PutS from C1 first (a notified eviction also leaves C1 in I); with no PutS, the Inv was delivered and the Inv-Ack was lost or never sent. Either way, H's ack counting did its job: the bug shows up as a hang, not as stale data. Compare the missed Inv of the previous lesson, where H did not wait and C1 read a stale value.

    Worked case B: a credit cycle

    Use the checkpoint of A Protocol Deadlock, with its roles: C1's GetM for X holds H's only pending entry P; C0's GetS for Y holds H's only ingress slot Q while it waits for P; C0's egress slot T0 holds Data(1) for X and waits for space in Q. The dump differs from case A in one way: every target has a record. P is held and waits for the data in T0; T0 holds it and waits for Q; Q is held by GetS(Y), which waits for P. The chain closes on itself, nothing is lost, and the newest value is safe in T0. The fix is a reserved response path, not a retransmission.

    Classify the dump mechanically

    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
    def classify(waits, owes):
        for start in waits:
            path, node = [], start
            while node in waits:
                if node in path:
                    return "deadlock: " + " -> ".join(path[path.index(node):] + [node])
                path.append(node)
                target, item = waits[node]
                if item not in owes.get(target, set()):
                    return f"lost message: {node} waits for {item} from {target}, which has no record of it"
                node = target
        return "no cycle and no dangling wait: compare two dumps for slow progress"
    def timeout_only(waits, owes):          # broken variant: every stuck entry is called deadlock
        return "deadlock" if waits else "ok"
    lost_inv = ({"C0.MSHR X": ("H.pend X", "Data X"), "H.pend X": ("C1", "Inv-Ack X")},
                {"H.pend X": {"Data X"}, "C1": set()})       # C1: S / 0, no MSHR, no Inv queued
    inv_ack_in_flight = (lost_inv[0], {"H.pend X": {"Data X"}, "C1": {"Inv-Ack X"}})
    credit_cycle = ({"C1.MSHR X": ("H.P", "Data X"), "H.P": ("C0.T0", "Data(1) X"),
                     "C0.T0": ("H.Q", "free slot"), "H.Q": ("H.P", "free entry")},
                    {"H.P": {"Data X", "free entry"}, "C0.T0": {"Data(1) X"}, "H.Q": {"free slot"}})
    a, b, c = classify(*lost_inv), classify(*inv_ack_in_flight), classify(*credit_cycle)
    assert a.startswith("lost message") and "from C1" in a
    assert b.startswith("no cycle")                 # the ack exists, it is only late
    assert c == "deadlock: H.P -> C0.T0 -> H.Q -> H.P"
    assert len({timeout_only(*d) for d in (lost_inv, inv_ack_in_flight, credit_cycle)}) == 1
    assert len({x.split(":")[0] for x in (a, b, c)}) == 3
    print("PASS: dumps separate a lost Inv, a late Inv-Ack and a credit cycle, a timeout cannot")

    The same edges give three different answers depending on what each target holds: a lost Inv (C1 has no record), a late Inv-Ack (C1 has it queued, so wait or look for backpressure), and a cycle. The broken variant, which calls every timeout a deadlock, gives one answer for all three.

    Common pitfall

    Reading MSHRs and pending entries while the system still runs. The entries change between reads, and the dump can show a cycle or a dangling wait that never existed at any one moment. Freeze first, or read twice and compare.

    Interview

    Asked 'the chip hangs, what next?', answer: freeze, dump H's pending entries and the caches' MSHRs for the line, draw who waits for what, and check whether the chain ends at an agent with no record (lost message) or closes on itself (deadlock). Then dump again to rule out livelock.

    Check yourself The dump shows H's entry 'pend GetM C0, acks 0/1' and C1 in I / - with an empty output queue. What happened, and where do you look next?

    First check H's input for a PutS from C1: an eviction also leaves C1 in I. With no PutS, C1 received and processed the Inv (its copy is gone), but the Inv-Ack never reached H: it was not sent or it was lost. Look at C1's Inv handling path and at the response queue and link between C1 and H, for example drop or error counters.

    Related topics

    • Shared course model

    • Previous: stale-data triage

    • Progress assumptions

    • What MSHRs and pending entries hold

    Previous

    136 / 167

    Next: Symptom to Mechanism

    Up next

    Up nextSymptom to Mechanism

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Trace-First Debug