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
  • 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
    • Capstone Brief
    • Stage 1: Memory Map and Interconnect
    • Stage 2: Clocks, Resets, Power
      • Clock Plan and Crossing Inventory
      • Reset Tree and Release Order
      • Power Domains, Isolation and Power-Up
    • Stage 3: Interrupts and Boot
    • Stage 4: Verification and Signoff
Loading lesson

←Stage 2: Clocks, Resets, Power
  1. Topics
  2. SoC Design
  3. Capstone: Integrate NanoSoC
  4. Stage 2: Clocks, Resets, Power
  5. Power Domains, Isolation and Power-Up
On this page

    Part 14 of 14, lesson 8 of 14Part 14 of 14/Capstone: Integrate NanoSoC/Lesson 8 of 14All levels

    Power Domains, Isolation and Power-Up

    File the domains and states, count the 114 clamps, walk the power-up timeline, and catch bugs 6 and 10.

    Why it matters

    Two failures share one stage. Entering SLEEP, irq_cpu goes high the moment the switch opens and the chip loops sleep and wake: 114 clamps are expected and only 112 are present, because the two interrupt lines float. And after an innocent CLK_EN write the CPU hangs on its first DMA access with no error: the gated DMA cannot drive PREADY and the bridge waits forever. Power work is counting plus sequencing, and this lesson does both.

    Goal, inputs and steps

    • Goal: the domain table, the 114-clamp count, both sequences with times, and two refusal rules.

    • Inputs: PD_AON against PD_SW, the four clamp groups, the 70-cycle down and 330-cycle up counts.

    • Steps: file the always-on list; list every clamped signal and audit it; walk both timelines; drop the interrupt clamps and confirm the audit flags them and the sleep model loops; gate an idle DMA clock and confirm the first access hangs without the watchdog and completes with PSLVERR with it.

    The core idea

    PD_AON holds RSTPWR, PLL, INTC, UART, SPI, GPIO, SRAM0 and ROM on 0.9 V forever; PD_SW holds the CPU, bridges, crossbar, SRAM1, DMA, SBR and APB decode behind a header switch, with SRAM0 arrays in AON and only their wrapper logic switched. One voltage means isolation only, no level shifters: 47 APB cdc outputs, 52 SRAM0 macro port bits, 13 ROM port bits and 2 interrupt lines, 114 clamp-to-0 cells driven by iso_sw_en. Power-down takes 70 clk_per (2.917 us): quiesce, isolate, gate clocks, assert resets, open the switch. Power-up takes 14,072.9 ns: 64-cycle ramp, 8-cycle pgood wait, 256-cycle relock, 2-cycle clock open, isolation release 4 clk_sys later, rst_sys_n after 18 clk_sys, rst_cpu_n after 8 clk_sys plus 2 clk_cpu. Going down: isolate, gate clocks, reset, open the switch. Coming up: close the switch, wait for power good, relock the PLL, open the clocks, release isolation while the resets are still asserted, then release rst_sys_n and last rst_cpu_n.

    The checker

    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
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    MAP = [
        ("BOOTROM", 0x00000000, 0x4000),
        ("SRAM0", 0x20000000, 0x10000),
        ("SRAM1", 0x20010000, 0x10000),
        ("DMA", 0x40000000, 0x1000),
        ("SBR", 0x40001000, 0x1000),
        ("UART", 0x40002000, 0x1000),
        ("SPI", 0x40003000, 0x1000),
        ("GPIO", 0x40004000, 0x1000),
        ("INTC", 0x40005000, 0x1000),
        ("RSTPWR", 0x40006000, 0x1000),
    ]
    SECURE_ONLY = {"SRAM1", "RSTPWR"}
    SLOTS = ["DMA", "SBR", "UART", "SPI", "GPIO", "INTC", "RSTPWR"]
    T_CPU, T_SYS, T_PER = 1e9 / 192e6, 1e9 / 96e6, 1e9 / 24e6
    def end_of(base, size):
        return base + size - 1
    def overlaps(entries=MAP):
        bad = []
        xs = sorted(entries, key=lambda e: e[1])
        for a, b in zip(xs, xs[1:]):
            if b[1] < a[1] + a[2]:
                bad.append((a[0], b[0]))
        return bad
    def misaligned(entries=MAP):
        return [n for n, b, s in entries if b % s != 0]
    def decode(addr, slots=SLOTS):
        if addr >> 14 == 0:
            return "BOOTROM"
        if addr >> 16 == 0x2000:
            return "SRAM0"
        if addr >> 16 == 0x2001:
            return "SRAM1"
        if addr >> 16 == 0x4000:
            slot = (addr >> 12) & 0xF
            return slots[slot] if slot < 7 else "H3"
        if addr < 0x20000000:
            return "H1"
        if addr < 0x40000000:
            return "H2"
        return "H4"
    def response(addr, secure, slots=SLOTS):
        t = decode(addr, slots)
        if t in ("H1", "H2", "H4"):
            return "DECERR"
        if t == "H3":
            return "SLVERR"
        if t in SECURE_ONLY and not secure:
            return "SLVERR"
        return "OK"
    def sustained(peak, beats):
        return peak * beats / (beats + 1)
    
    # ---- this lesson starts here ----
    
    GROUPS = {"apb_cdc": [("paddr", 12), ("pwdata", 32), ("psel", 1), ("penable", 1), ("pwrite", 1)],
              "sram0": [("addr", 14), ("wdata", 32), ("be", 4), ("ce", 1), ("we", 1)],
              "rom": [("addr", 12), ("ce", 1)], "irq": [("irq_dma", 1), ("irq_sbr", 1)]}
    def signals():
        return [g + "." + n + "[" + str(i) + "]" for g, items in GROUPS.items() for n, w in items for i in range(w)]
    def clamp_audit(clamped):
        return [s for s in signals() if s not in clamped]
    def sleep_effects(missing):
        fx = set()
        if any(s.startswith("irq.") for s in missing):
            fx.add("spurious irq_cpu, sleep and wake loop")
        if any(s.startswith("sram0.") for s in missing):
            fx.add("X on the SRAM0 macro port")
        return fx
    ALL = set(signals())
    assert len(ALL) == 114 and clamp_audit(ALL) == []
    assert {g: sum(w for _, w in v) for g, v in GROUPS.items()} == {"apb_cdc": 47, "sram0": 52, "rom": 13, "irq": 2}
    no_irq = {s for s in ALL if not s.startswith("irq.")}                 # bug 6
    assert len(no_irq) == 112 and clamp_audit(no_irq) == ["irq.irq_dma[0]", "irq.irq_sbr[0]"]
    assert sleep_effects(clamp_audit(no_irq)) == {"spurious irq_cpu, sleep and wake loop"}
    assert sleep_effects(clamp_audit(ALL - {"sram0.wdata[7]"})) == {"X on the SRAM0 macro port"} and sleep_effects([]) == set()
    down = [("isolate", 2), ("gate_clocks", 2), ("assert_resets", 2), ("open_switch", 64)]
    assert [n for n, _ in down] == ["isolate", "gate_clocks", "assert_resets", "open_switch"]
    assert abs(sum(c for _, c in down) * T_PER - 2916.7) < 1.0
    def wake():
        t = {"pgood": (64 + 8) * T_PER}
        t["clocks_open"] = t["pgood"] + (256 + 2) * T_PER
        t["iso_release"] = t["clocks_open"] + 4 * T_SYS
        t["rst_sys_n"] = t["iso_release"] + 18 * T_SYS
        t["rst_cpu_n"] = t["rst_sys_n"] + 8 * T_SYS + 2 * T_CPU
        return t
    w = wake()
    assert abs(w["rst_cpu_n"] - 14072.9) < 1.0
    assert w["clocks_open"] < w["iso_release"] < w["rst_sys_n"] < w["rst_cpu_n"]     # isolation drops while resets are still asserted
    def apb_access(clk_en, wdog):
        for cycle in range(1, 2000):
            if clk_en and cycle >= 3:
                return "OK"                                  # a DMA access takes 3 clocks
            if wdog and cycle >= wdog:
                return "PSLVERR"                             # the bridge watchdog gives up
        return "HANG"
    def clk_en_write(busy, old, new):
        return old if busy else new                          # refuse to gate a block that is BUSY
    assert apb_access(True, None) == "OK"
    assert apb_access(False, None) == "HANG"                 # bug 10: idle DMA gated, no watchdog
    assert apb_access(False, 256) == "PSLVERR" and abs(256 * T_SYS / 1000 - 2.667) < 0.001
    assert clk_en_write(True, 0x3, 0x0) == 0x3 and clk_en_write(False, 0x3, 0x0) == 0x0
    assert apb_access(clk_en_write(False, 0x3, 0x0) & 1 == 1, None) == "HANG"    # the BUSY rule alone does not fix the idle case
    print("PASS: 114 clamps audited; 112 flagged with a wake loop; wake 14.073 us; gated idle DMA hangs without the 256-cycle watchdog")

    Expected output

    One PASS line. The no_irq set is the bug-6 demonstration: the audit names exactly the two interrupt clamps and the sleep model returns the wake loop. The apb_access calls are the bug-10 demonstration: gating an idle DMA hangs the access, the watchdog turns it into PSLVERR after 256 clk_sys cycles, and the last assert shows that refusing to gate a BUSY block is a separate rule that does not rescue the idle case.

    What each failure means

    A clamp failure means a group was mis-sized: APB outputs are 12 plus 32 plus 3, SRAM0 is 14 plus 32 plus 4 plus 2, ROM is 12 plus 1. A timeline failure means cycles were charged to the wrong domain, or isolation was released after the resets instead of before. A wrong apb_access result means the watchdog was modelled as a refusal.

    Hint ladder

    Hint 1: count every signal from PD_SW into PD_AON, including the two that look like control. Hint 2: isolation asserts before reset and power off; on wake it releases 4 clk_sys after the clocks open, while the resets are still asserted. Hint 3: a bridge with no timeout turns a gated clock into a hang, and it hangs on an idle block as readily as a busy one. Answer: clamp-0 on all 114 nets via iso_sw_en, and a 256-cycle watchdog in the AXI-to-APB bridge completing with PSLVERR; a rule that refuses to gate a BUSY block's clock protects a job in flight but is not the fix for the hang.

    Wake logic fed from the switched domain: a controller needed for wake-up that sits behind the switch can never wake the chip. Wake sources, the PMU path and the reset controller stay always-on.

    If asked for the power sequence in one breath, give isolate, gate, reset, switch on the way down, and ramp, good, lock, clocks, isolation, resets on the way up, with 2.917 us down and 14.073 us up.

    Check yourself SLEEP keeps clk_per running. Which three duties does it serve?

    The reset controller and wake logic, the always-on peripherals that can wake the chip, and the power-switch and isolation sequencer itself.

    Related topics

    • Back: Reset Tree and Release Order

    • Stage 3: Interrupts and Boot

    • Low Power: Isolation Strategies

    Previous

    273 / 289

    Next: Stage 3: Interrupts and Boot

    Up next

    Up nextStage 3: Interrupts and Boot

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Clock Plan and Crossing Inventory