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. Clock Plan and Crossing Inventory
On this page

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

    Clock Plan and Crossing Inventory

    Derive the PLL chain, tabulate the ten crossings, and catch planted bug 5.

    Why it matters

    Once in a few thousand DMA jobs the interrupt never arrives, and INTC RAW flickers for one cycle. Every simulation passes. The irq_dma line at 96 MHz feeds a flop in the 24 MHz controller with no synchroniser, so setup or hold fails just often enough to ruin a week. Structural crossing inventory, not simulation, is the catcher.

    Goal, inputs and steps

    • Goal: the clock derivation plus a ten-row crossing table where every row names its mechanism.

    • Inputs: 24 MHz crystal, R=1, N=32, dividers /4 and /8, the crossing list.

    • Steps: derive the three frequencies; file each crossing with its width and mechanism; remove one synchroniser, then give a 32-bit bus a 2-flop synchroniser, and confirm the audit flags each; restore.

    Crossing

    From to to

    Mechanism

    X1/X2

    clk_cpu to/from clk_sys

    Async AXI bridge, 4-deep FIFO per channel, gray pointers

    X3/X4

    clk_sys to/from clk_per

    APB req/ack handshake, data held stable

    X5

    clk_sys to clk_per

    2-flop level sync on irq_dma, irq_sbr

    X6

    clk_per to clk_cpu

    2-flop level sync on irq_cpu

    X7/X8

    clk_per to clk_sys/cpu

    Reset sync release per domain; pll_lock via 2 flops

    X9

    pads to clk_per

    2-flop sync per external input

    X10

    clk_per to clk_sys

    2-flop sync on pwr_up_done, pgood

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

    Expected output

    One PASS line. The audit of the clean list is the deliverable; the three modified rows are the failing-state demonstrations. The bare irq_dma row is bug 5 exactly; the 32-bit bus and the single flop show the audit judges the mechanism against the width and depth, not against a vocabulary word.

    What each failure means

    A chain failure means R or N was misapplied: VCO is 24 times 32 over 1, and clk_per bypasses the PLL entirely. A count failure means a crossing was merged or split: X1/X2 and X3/X4 are pairs but count as four rows. An audit hit on clean data means a row has the wrong mechanism for its width: one bit needs two flops, a bus needs a FIFO or a handshake, and a reset needs a reset synchroniser.

    Hint ladder

    Hint 1: single-bit levels need two flops, multi-bit needs a FIFO or a handshake, resets need per-domain sync release. Hint 2: the failing path is a level held long, so a pulse synchroniser is the wrong fix. Hint 3: the flop in clk_per samples a signal launched by an unrelated clock, so some edges violate setup or hold; the second flop gives the first a full period to settle. Answer: add nano_sync_2ff on every INTC input, require inputs held at least 2 T_per, waive nothing in CDC lint.

    Treating same-frequency clocks from different roots as synchronous. Without a bounded phase relation the tools must see them as asynchronous, or signoff passes and silicon fails at speed.

    If asked why simulation never caught it, answer with rarity arithmetic: metastability needs a setup or hold violation at one flop, which needs alignment of two unrelated edges, so thousands of jobs pass between hits and only the structural check is reliable.

    Check yourself clk_per bypasses the PLL. What breaks if it did not?

    SLEEP with the PLL off would stop the reset controller, the wake logic and the slow APB segment, so the chip could never wake.

    Related topics

    • Next: Reset Tree and Release Order

    • CDC and RDC: Async FIFO CDC

    • CDC and RDC: Synchroniser Selection Guide

    Previous

    271 / 289

    Next: Reset Tree and Release Order

    Up next

    Up nextReset Tree and Release Order

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Power Domains, Isolation and Power-Up