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
    • Clock Sources and PLLs
    • Dividers, Muxes and Glitch-Free Switching
    • Clock Gating Architecture
    • Clock Domain Planning
      • Choosing Domains and Ratios
      • Phase-Related Versus Asynchronous Signoff
      • Domain Table and Ownership
    • Top-Level Crossings
    • Always-On Domain, Wake Policy and a Timer Extension
    • Reset Sources and Reset Tree
    • Reset Sequencing and Release
  • 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

←Clock Domain Planning
  1. Topics
  2. SoC Design
  3. Clock and Reset Architecture
  4. Clock Domain Planning
  5. Domain Table and Ownership
On this page

    Part 5 of 14, lesson 12 of 24Part 5 of 14/Clock and Reset Architecture/Lesson 12 of 24All levels

    Domain Table and Ownership

    Every block in exactly one domain: the NanoSoC domain table, bridge halves, and the check that proves it.

    Why it matters

    A block with no domain gets no clock tree, no reset synchroniser and no crossing rule; a block in two domains gets wired to the wrong one somewhere. The domain table is the one page that prevents both, and the crossing inventory is generated from it. This lesson writes the NanoSoC table in full and gives the script that audits it. Its one job is the table; the crossings it implies belong to the next topic.

    The core idea

    Each row names a block, its clock and its power domain, because clock and power travel together in the checks. Bridges appear twice, once per side: the AXI bridge is clk_cpu on the CPU side and clk_sys on the system side, the APB bridge is clk_sys on one side and clk_per on the other. Both bridges sit in PD_SW, which is why the APB bridge's clk_per-side outputs need isolation cells into PD_AON. The table below follows the NanoSoC specification; clk_cpu is 192 MHz, clk_sys 96 MHz and clk_per 24 MHz:

    Block (side)

    Clock

    Power

    nano_cpu core and I-cache

    clk_cpu

    PD_SW

    nano_axi_cdc_m (CPU side)

    clk_cpu

    PD_SW

    nano_axi_cdc_m (system side)

    clk_sys

    PD_SW

    nano_xbar, nano_dma, nano_sbr, nano_sram1, nano_axi2apb, nano_apb_dec

    clk_sys

    PD_SW

    nano_apb_cdc (system side)

    clk_sys

    PD_SW

    nano_sram0 (arrays; its wrapper logic is in PD_SW), nano_rom

    clk_sys

    PD_AON

    nano_apb_cdc (peripheral side)

    clk_per

    PD_SW

    nano_uart, nano_spi, nano_gpio, nano_intc, nano_rstpwr

    clk_per

    PD_AON

    Ownership means the table has one writer: the integrator. Block owners may request a domain change, but the table changes only with the crossing inventory updated in the same edit, because every moved block adds crossings on one side and removes them on the other. The audit below checks the two invariants a reviewer cannot check by eye across revisions: every block appears exactly once per side, and every bridge side has a partner side in a different domain with a named mechanism.

    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
    # (block-side, clock, power): the table above, one row per block-side.
    ROWS = [
        ("nano_cpu", "clk_cpu", "PD_SW"), ("axi_cdc_m.cpu", "clk_cpu", "PD_SW"),
        ("axi_cdc_m.sys", "clk_sys", "PD_SW"), ("nano_xbar", "clk_sys", "PD_SW"),
        ("nano_sram0", "clk_sys", "PD_AON"), ("nano_sram1", "clk_sys", "PD_SW"),
        ("nano_rom", "clk_sys", "PD_AON"), ("nano_dma", "clk_sys", "PD_SW"),
        ("nano_sbr", "clk_sys", "PD_SW"), ("nano_axi2apb", "clk_sys", "PD_SW"),
        ("nano_apb_dec", "clk_sys", "PD_SW"), ("nano_apb_cdc.sys", "clk_sys", "PD_SW"),
        ("nano_apb_cdc.per", "clk_per", "PD_SW"), ("nano_uart", "clk_per", "PD_AON"),
        ("nano_spi", "clk_per", "PD_AON"), ("nano_gpio", "clk_per", "PD_AON"),
        ("nano_intc", "clk_per", "PD_AON"), ("nano_rstpwr", "clk_per", "PD_AON"),
    ]
    BRIDGES = [("axi_cdc_m.cpu", "axi_cdc_m.sys", "async FIFO"),
               ("nano_apb_cdc.sys", "nano_apb_cdc.per", "req/ack handshake")]
    ALWAYS_ON = {"nano_rstpwr", "nano_intc", "nano_uart", "nano_spi", "nano_gpio",
                 "nano_sram0", "nano_rom"}  # power domain list (PLL is a macro)
    
    def audit(rows, bridges):
        errors = []
        names = [r[0] for r in rows]
        if len(names) != len(set(names)):
            errors.append("a block sits in two rows")
        clock = {r[0]: r[1] for r in rows}
        power = {r[0]: r[2] for r in rows}
        for a, b, mech in bridges:
            if a not in clock or b not in clock:
                errors.append("bridge side missing: " + a + "/" + b)
            elif clock[a] == clock[b]:
                errors.append("bridge sides share a clock: " + a)
            elif mech == "none":
                errors.append("bridge without mechanism: " + a)
        for blk in ALWAYS_ON:
            if power.get(blk) != "PD_AON":
                errors.append(blk + ": not PD_AON")
            if blk != "nano_sram0" and blk != "nano_rom" and clock.get(blk) != "clk_per":
                errors.append(blk + ": always-on control must be on clk_per")
        return errors
    
    assert len(ROWS) == 18
    assert audit(ROWS, BRIDGES) == []
    # The audit can fail: each deliberately wrong input is rejected.
    assert audit(ROWS + [("nano_uart", "clk_sys", "PD_AON")], BRIDGES) != []   # two rows
    assert audit([r for r in ROWS if r[0] != "nano_apb_cdc.per"], BRIDGES) != []  # lost side
    assert audit(ROWS, [("axi_cdc_m.cpu", "axi_cdc_m.sys", "none")]) != []     # no mechanism
    assert audit([("nano_gpio", "clk_sys", "PD_SW") if r[0] == "nano_gpio" else r
                  for r in ROWS], BRIDGES) != []                                # moved GPIO
    print("PASS: 18 block-sides placed once; 2 bridges have mechanisms; 4 bad tables rejected")

    What the review looks for

    • The table above, current, with a version shared by the crossing inventory and the constraint modes.

    • Bridge halves listed as separate rows; no bridge drawn as living in one domain.

    • The audit output attached to the review, rerun after every hierarchy change.

    Verify and debug

    Diff the table against the netlist clock pins with a connectivity checker: every flop's clock must trace to its row's domain, and any pin traced elsewhere is either a table bug or a wiring bug, both fixed in the table first. A crossing that appears in silicon but not in the inventory always traces to a row that moved without its inventory update.

    A block missing from the table gets a clock but no reset synchroniser, and no crossing rule: it works in zero-delay simulation and fails at release or at the first crossing in silicon. The audit above catches it because every flop's block must have a row, and the table feeds the reset-synchroniser list as well as the crossing inventory.

    If asked to plan domains for an unfamiliar chip, start from this table's shape: three rows, bridge halves split, power beside clock. Then explain what you would add first and what it costs in crossings.

    Check yourself The GPIO block is proposed to move to clk_sys for faster sampling. What breaks?

    Its interrupt path into nano_intc changes domains, its APB segment moves across the APB bridge boundary, and its wake role dies: clk_sys stops in sleep while clk_per runs. Faster sampling is bought with a lost wake source.

    Related topics

    • Phase-Related Versus Asynchronous Signoff

    • Next: The Ten Crossings

    • PD: Multi-Clock-Domain CTS

    Previous

    102 / 289

    Next: Top-Level Crossings

    Up next

    Up nextTop-Level Crossings

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Choosing Domains and Ratios