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
  • The NanoSoC Tour
  • Reading Path
  • SoC Glossary10
  • SoC Metrics Reference
  • SoC Cheatsheet
  • What SoC Integration Is12
  • Architecture and Budgets21
  • Memory Maps and Address Decode26
  • Interconnect and Bus Fabrics26
    • Topologies and Bridges
    • AXI Integration Details
    • Ordering and Deadlock at the Top
      • What Ordering the Integrator Owes
      • How Deadlock Happens at SoC Level
      • Proving Freedom and Debugging Hangs
    • QoS and Arbitration
    • NoC Basics
    • Coherent Interconnect View
    • Firewalls and Protection
    • Bandwidth and Latency Verification
  • Clock and Reset Architecture24
  • Power Architecture21
  • Interrupts, DMA and Peripherals15
  • Memories, Boot and Security21
  • IP Integration and Handoff21
  • Top-Level Verification and Bring-up21
  • Package, Pads and I/O18
  • Chip Signoff and Tapeout18
  • SoC Interview Prep16
  • Capstone: Integrate NanoSoC14
Loading lesson

←Ordering and Deadlock at the Top
  1. Topics
  2. SoC Design
  3. Interconnect and Bus Fabrics
  4. Ordering and Deadlock at the Top
  5. Proving Freedom and Debugging Hangs
On this page

    Part 4 of 14, lesson 10 of 26Part 4 of 14/Interconnect and Bus Fabrics/Lesson 10 of 26All levels

    Proving Freedom and Debugging Hangs

    The AXI rules that make a path dependency-safe, the NanoSoC chain checked against them, and the triage when prevention failed.

    Why it matters

    A hang found in emulation costs weeks; the same hang found by a dependency review costs an afternoon. The integrator's prevention is structural, and the debug is mechanical. When a chip does freeze, check clocks and resets first: it takes a minute and rules out the commonest cause. Only then is the hang a dependency problem, and this lesson gives the rules and the method for that case.

    The core idea

    A path is provably free of dependency cycles when every wait in it is allowed by the AXI handshake rules and the chain of waits ends at a component that always answers. The public rules are short. A master must not wait for AWREADY, WREADY or ARREADY before asserting AWVALID, WVALID or ARVALID. A slave may wait for AWVALID or WVALID (or both) before asserting AWREADY or WREADY. A slave must wait for both address and data handshakes (up to WLAST) before asserting BVALID, and for the address handshake before RVALID. A slave must not wait for BREADY or RREADY before asserting BVALID or RVALID; a master may wait for BVALID or RVALID before asserting BREADY or RREADY. Together they give a one-way order: AR before R, AW and W before B, and no edge back from a response to a request.

    The integrator's job is to check that each component added to the fabric obeys them and adds no edge of its own. Walk the NanoSoC write to a UART register: the CPU waits for B; the axi_cdc passes it through five independent FIFOs; the xbar routes it; the axi2apb bridge accepts one access at a time and waits for PREADY; the apb_cdc runs one request and acknowledge handshake; the UART answers with zero wait states. The only places that serialise are axi2apb and apb_cdc, and both wait on an APB slave that always answers (PREADY within a wait state, or the 256-cycle watchdog), and no APB slave ever waits on the fabric. That is the freedom argument: the chain of waits ends, and no wait points back.

    Diagram
    # not-run: NanoSoC deadlock-prevention checklist, NanoSoC teaching values
    1. axi_cdc: independent, five channel FIFOs (AW, W, AR, B, R).
       axi2apb and apb_cdc: one access at a time by design. Safe because the only
       thing they wait on is an APB slave that always answers (PREADY or the
       256-cycle watchdog), and no APB slave ever waits on the fabric.
    2. Shared buffers: 4-deep FIFOs per AXI channel, one APB beat at a time. A bounded
       buffer is full eventually either way; freedom comes from item 1, not from depth.
    3. Power-down mid-transaction refused: gate blocked while BUSY, watchdog answers SLVERR.
    4. Reset joins bridges: rst_sys_n and rst_cpu_n asserted together, released slaves first.
    5. Default slaves answer every address: no waiter without a responder.
    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
    43
    44
    45
    # NanoSoC teaching values: the wait chain of a CPU write to a UART register.
    # An edge a -> b means a cannot complete until b does.
    CHAIN = {
        "cpu_write": ["axi_cdc_aw_w_fifos"],
        "axi_cdc_aw_w_fifos": ["xbar_route"],
        "xbar_route": ["axi2apb_access"],
        "axi2apb_access": ["apb_cdc_handshake", "watchdog"],
        "apb_cdc_handshake": ["uart_pready"],
        "uart_pready": [], # zero wait states on clk_per slaves: always answers
        "watchdog": [], # 256 clk_sys cycles, then SLVERR
    }
    
    def acyclic(graph):
        state = {}
        def visit(n):
            if state.get(n) == 1:
                return False
            if state.get(n) == 2:
                return True
            state[n] = 1
            ok = all(visit(m) for m in graph.get(n, []))
            state[n] = 2
            return ok
        return all(visit(n) for n in graph)
    
    def every_wait_ends(graph, start):
        # follow the waits: they must reach a node with no further wait
        seen, stack = set(), [start]
        while stack:
            n = stack.pop()
            if n in seen:
                continue
            seen.add(n)
            stack.extend(graph.get(n, []))
        return any(not graph.get(n) for n in seen)
    
    assert acyclic(CHAIN) and every_wait_ends(CHAIN, "cpu_write")
    # inject the property that must never exist: an APB slave that waits on the fabric
    bad = dict(CHAIN)
    bad["uart_pready"] = ["cpu_write"]
    assert not acyclic(bad), "a slave waiting on the fabric must be caught"
    # and a bridge without its watchdog and a slave that never answers has no end
    dead = {"cpu_write": ["axi2apb_access"], "axi2apb_access": ["dead_slave"], "dead_slave": ["dead_slave"]}
    assert not acyclic(dead)
    print("PASS: NanoSoC write chain is acyclic and ends at an always-answering slave; two injected faults are caught")

    What the review looks for

    • The channel-dependency review is a signed artefact, not a hallway conversation, listing each bridge, what it serialises and where its chain of waits ends.

    • Formal connectivity and liveness checks cover illegal-address and secure-fault traffic, not just the happy path.

    • Power-down and reset sequences list their quiesce steps with the transactions that must drain first.

    Verify and debug

    When the fabric freezes, work the triage in order: confirm clocks and resets are good (a minute, and the commonest cause), then dump outstanding transactions per master and per bridge. Draw the waiter ring; break it by completing one member by hand; the member whose completion unwinds the rest names the fix. Escalate from simulation to emulation only with the traffic mix that reproduces it, and carry the same dump-and-ring method there. For the hang-debugging craft see the hardware-debug course; for coherent-fabric variants see the virtual-network lesson it links.

    Partial reset hang across a reset-domain crossing: one side of a bridge is reset mid-transaction while the other waits for a response forever. Caught by reset-domain analysis and directed quiesce tests; fixed by draining first and resetting both sides together. The same holds for power-down mid-transaction, which is why the watchdog exists.

    If asked to debug a hung chip, start with progress not causes: check clocks and resets, list what still completes, dump the waiters, draw the ring. Interviewers score the structured triage higher than any guessed root cause.

    Check yourself The waiter ring includes a power-gated block. What sequence manufactured it?

    Power was cut with transactions in flight, so a live master waits on a response from a dead slave. The NanoSoC power-down order exists to prevent this: software quiesces the DMA and SBR, the clamp is asserted, the clocks are gated, resets assert, and only then does the power switch open.

    Related topics

    • How Deadlock Happens at SoC Level

    • Protocol First Principles: Proving Deadlock Freedom

    • Protocol First Principles: Message-Dependent Deadlock

    • Hardware Debug: Hangs and Deadlocks

    Previous

    74 / 289

    Next: QoS and Arbitration

    Up next

    Up nextQoS and Arbitration

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • What Ordering the Integrator Owes