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 NanoSoC19
    • Capstone Brief
    • Stage 1: Memory Map and Interconnect
    • Stage 2: Clocks, Resets, Power
    • Stage 3: Interrupts and Boot
    • Stage 4: Verification and Signoff
    • Solutions
      • Root Causes, Fixes and Evidence
      • Stage 1 Worked Checks
      • Stage 2 Worked Checks
      • Stage 3 Worked Checks
      • Stage 4 Worked Checks
Loading lesson

←Solutions
  1. Topics
  2. SoC Design
  3. Capstone: Integrate NanoSoC
  4. Solutions
  5. Stage 2 Worked Checks
On this page

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

    Stage 2 Worked Checks

    Worked answer: runnable checks and explicit fault injections after the challenge stages.

    These are the worked solutions. Compare the inputs and injected faults with your written hypothesis before running each block. Each block runs independently and prints one PASS line after its clean-data and failing-data assertions.

    Worked check 1

    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
    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 ----
    
    def chain(xtal=24, r=1, n=32, d_cpu=4, d_sys=8):
        vco = xtal * n / r
        assert 600 <= vco <= 1200               # the teaching VCO range
        return vco, vco / d_cpu, vco / d_sys, xtal
    assert chain() == (768.0, 192.0, 96.0, 24) and chain(n=30) != chain()
    assert abs(T_PER - 41.6667) < 0.001 and abs(256 * T_PER / 1000 - 10.667) < 0.01
    # id, from, to, signal, width in bits, mechanism, flops on the receiving side
    XING = [("X1", "clk_cpu", "clk_sys", "axi_req", 32, "fifo", 2), ("X2", "clk_sys", "clk_cpu", "axi_resp", 32, "fifo", 2),
            ("X3", "clk_sys", "clk_per", "apb_req", 32, "reqack", 2), ("X4", "clk_per", "clk_sys", "apb_resp", 32, "reqack", 2),
            ("X5", "clk_sys", "clk_per", "irq_dma", 1, "2ff", 2), ("X6", "clk_per", "clk_cpu", "irq_cpu", 1, "2ff", 2),
            ("X7", "clk_per", "clk_sys", "rst_sys_n", 1, "rstsync", 2), ("X8", "clk_per", "clk_cpu", "rst_cpu_n", 1, "rstsync", 2),
            ("X9", "pad", "clk_per", "gpio_in", 1, "2ff", 2), ("X10", "clk_per", "clk_sys", "pgood", 1, "2ff", 2)]
    def audit(rows):
        bad = []
        for x, src, dst, sig, width, mech, flops in rows:
            if sig.startswith("rst"):
                good = mech == "rstsync" and flops >= 2
            elif width == 1:
                good = mech == "2ff" and flops >= 2      # one bit: two flops, no fewer
            else:
                good = mech in ("fifo", "reqack")         # many bits: a FIFO or a handshake, never a synchroniser per bit
            if not good:
                bad.append(x)
        return bad
    assert len(XING) == 10 and audit(XING) == []
    def with_row(i, row):
        return XING[:i] + [row] + XING[i + 1:]
    assert audit(with_row(4, ("X5", "clk_sys", "clk_per", "irq_dma", 1, "direct", 0))) == ["X5"]     # bug 5
    assert audit(with_row(2, ("X3", "clk_sys", "clk_per", "apb_req", 32, "2ff", 2))) == ["X3"]       # a bus with a bit synchroniser
    assert audit(with_row(5, ("X6", "clk_per", "clk_cpu", "irq_cpu", 1, "2ff", 1))) == ["X6"]        # one flop is not a synchroniser
    print("PASS: 768/192/96/24 MHz chain; 10 crossings audited by width; bare X5, bus on 2ff and 1-flop X6 all flagged")

    Worked check 2

    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
    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 ----
    
    # each net: (what it waits for, how long after that), from the R1 to R6 table
    PLAN = {"rst_per_n": ("por", 1026 * T_PER),                    # 1024 oscillator cycles plus a 2-flop sync
            "rst_sys_n": ("gates", 18 * T_SYS),                     # 16-cycle hold plus a 2-flop sync in clk_sys
            "rst_cpu_n": ("rst_sys_n", 8 * T_SYS + 2 * T_CPU)}      # 8-cycle gap plus a 2-flop sync in clk_cpu
    def release_times(plan):
        t = {"por": 0.0}
        t["rst_per_n"] = plan["rst_per_n"][1]
        t["gates"] = t["rst_per_n"] + 262 * T_PER                   # PLL start and lock (260) and gates open (2)
        for net in ("rst_sys_n", "rst_cpu_n"):
            after, wait = plan[net]
            t[net] = t[after] + wait
        return t
    def fetch_lost(t):
        # the first fetch reaches the crossbar 1 clk_cpu + 3 clk_sys after the CPU leaves reset
        return t["rst_cpu_n"] + T_CPU + 3 * T_SYS < t["rst_sys_n"]
    def order_ok(t):
        return t["rst_per_n"] <= t["rst_sys_n"] and t["rst_cpu_n"] >= t["rst_sys_n"] + 8 * T_SYS
    good = release_times(PLAN)
    assert abs(good["rst_cpu_n"] / 1000 - 53.948) < 0.01
    assert abs(good["rst_sys_n"] - good["gates"] - 187.5) < 0.1 and abs(good["rst_cpu_n"] - good["rst_sys_n"] - 93.75) < 0.1
    assert order_ok(good) and not fetch_lost(good)
    bug = release_times(dict(PLAN, rst_cpu_n=("gates", 2 * T_CPU)))   # bug 2: released in the same step as rst_sys_n
    assert not order_ok(bug) and fetch_lost(bug)
    assert abs(bug["rst_sys_n"] - bug["rst_cpu_n"] - 177.1) < 0.1
    print("PASS: CPU released at 53.948 us, 93.75 ns after the fabric; same-step release breaks the order and loses the fetch")

    Worked check 3

    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")

    The final PASS establishes consistency of the Python model and manifest. Record the known SBR attribute weakness and all implementation evidence still required in the readiness statement. The model reproduces an open partition in v1 and demonstrates a proposed secure-write guard; a model PASS alone does not close that finding.

    Related topics

    • Solution key and closure evidence

    • Next: Stage 3 Worked Checks

    Previous

    282 / 294

    Next: Stage 3 Worked Checks

    Up next

    Up nextStage 3 Worked Checks

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Root Causes, Fixes and Evidence
    • Stage 4 Worked Checks