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
    • IP Collateral and Versioning
    • Wrappers and Adapters
    • Integration Lint and Connectivity Checks
    • Top-Level SDC and Constraint Merge
      • Clock Definitions Belong at the Top
      • Merging Block Constraints
      • Exceptions and Their Review
    • DFT Integration
    • Debug Infrastructure
    • Safety and Fault Architecture
  • Top-Level Verification and Bring-up21
  • Package, Pads and I/O18
  • Chip Signoff and Tapeout18
  • SoC Interview Prep16
  • Capstone: Integrate NanoSoC14
Loading lesson

←Top-Level SDC and Constraint Merge
  1. Topics
  2. SoC Design
  3. IP Integration and Handoff
  4. Top-Level SDC and Constraint Merge
  5. Clock Definitions Belong at the Top
On this page

    Part 9 of 14, lesson 10 of 21Part 9 of 14/IP Integration and Handoff/Lesson 10 of 21All levels

    Clock Definitions Belong at the Top

    One PLL, three domains, one author: the top-level clock definitions recomputed from the PLL plan.

    Why it matters

    The DMA was verified against a 96 MHz assumption, but nothing in its file knows that 96 MHz comes from a 768 MHz VCO divided by eight, or that the 192 MHz CPU clock shares the same VCO through a divide by four, or that the 24 MHz peripheral clock bypasses the PLL entirely. When the PLL plan changes, every block constraint written against the old frequencies lies. The fix is top-level ownership: the clock definitions are written once at the top from the PLL plan, and block files reference them instead of defining their own.

    The core idea

    The top file defines the source first: the 24 MHz crystal, the multiply by 32 to the 768 MHz VCO, and the divides that produce 192 and 96 MHz. Both dividers are declared as generated clocks of the PLL output. Because signoff treats clk_cpu and clk_sys as asynchronous (separate clock trees after the divider), the file also declares them as an asynchronous clock group; otherwise the tool, seeing one master and two divide relationships, would time the crossings inside the asynchronous AXI bridge as synchronous paths and report violations that cannot happen in silicon. clk_per is the crystal clock itself, defined once at the crystal pin; it is the source clock, not a second clock, and it keeps running when the PLL is off in sleep. Block files must not redefine any of this: their clock assumptions become comments pointing at the top definitions, and any block file that still creates a clock with the same name fails the merge check the next lesson runs.

    Diagram
    # not-run: top-level clock definitions, NanoSoC teaching values
    source: clk_xtal 24.000 MHz, period 41.667 ns
    VCO: 24 x 32 / 1 = 768 MHz (inside the 600 to 1200 MHz teaching range)
    clk_cpu: VCO / 4 = 192 MHz, period 5.208 ns
    clk_sys: VCO / 8 = 96 MHz, period 10.417 ns
    clk_per: the crystal clock itself, defined once at the crystal pin, 24 MHz, period 41.667 ns
    asynchronous group: clk_cpu and clk_sys (separate trees; crossings are FIFO-protected)
    PLL lock: 256 clk_per cycles = 10.667 us, qualified before gates open
    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
    # Recompute every NanoSoC clock definition from the PLL plan.
    XTAL_MHZ, N, R = 24.0, 32, 1
    VCO_MHZ = XTAL_MHZ * N / R
    assert VCO_MHZ == 768.0
    assert 600.0 <= VCO_MHZ <= 1200.0  # teaching VCO range
    clocks = {"clk_cpu": VCO_MHZ / 4, "clk_sys": VCO_MHZ / 8,
              "clk_per": XTAL_MHZ}  # bypasses the PLL
    assert clocks == {"clk_cpu": 192.0, "clk_sys": 96.0, "clk_per": 24.0}
    periods = {name: 1000 / f for name, f in clocks.items()}  # ns
    assert abs(periods["clk_cpu"] - 5.2083) < 0.001
    assert abs(periods["clk_sys"] - 10.4167) < 0.001
    assert abs(periods["clk_per"] - 41.6667) < 0.001
    lock_us = 256 / clocks["clk_per"]  # MHz -> us per cycle
    assert abs(lock_us - 10.6667) < 0.001
    
    # Both dividers come from one master, so a tool relates them unless told not to.
    GENERATED_FROM = {"clk_cpu": "pll_out", "clk_sys": "pll_out"}
    def timed_as_synchronous(a, b, async_groups):
        related = GENERATED_FROM.get(a) is not None and GENERATED_FROM.get(a) == GENERATED_FROM.get(b)
        split = any(a in g1 and b in g2 for g1 in async_groups for g2 in async_groups if g1 is not g2)
        return related and not split
    assert timed_as_synchronous("clk_cpu", "clk_sys", [])                       # missing group: wrong
    assert not timed_as_synchronous("clk_cpu", "clk_sys", [{"clk_cpu"}, {"clk_sys"}])
    print("PASS: 192/96/24 MHz with periods; lock 10.667 us; missing async group is detectable")

    What the review looks for

    • Exactly one definition per clock in the merged constraints, authored at the top, with the PLL arithmetic above attached.

    • No block file defining a clock: the merge check fails any file that does.

    • The PLL-bypass clock visibly independent, so sleep-mode timing still constrains the wake logic when the PLL is off.

    Verify and debug

    Diff the clock names against the clock-controller documentation: a renamed divider output with old constraints still referencing it constrains nothing and fails loudly only if the review diffs names. When timing degrades after a PLL-plan change, recheck the divide relationships before the paths; a divide-by value edited in one place and not the other doubles or halves every derived period silently.

    Missing asynchronous clock group: the 192 MHz and 96 MHz clocks are both generated from the PLL output, the group is never declared, and the tool times the crossings inside nano_axi_cdc_m as synchronous paths. The report fills with violations that cannot occur, someone waives them with a broad false path, and the same waiver also covers a real path. Caught by checking that every pair of clocks is either related and timed, or declared asynchronous; fixed by declaring the group once, at the top, with the crossing mechanism named beside it.

    If asked who owns clock definitions in a hierarchical flow, answer: the top, always, derived from the PLL plan. Blocks describe the clocks they assumed; the top re-declares them and the merge deletes the block copies. Then quote the NanoSoC divides: 32 up, 4 and 8 down, crystal straight through.

    Check yourself A block file defines its own 96 MHz clock with the same name as the top clock. Harmless duplicate or merge failure?

    Merge failure. Two definitions of one name disagree the day either changes, and tools resolve the conflict by rules nobody reviewed. Delete the block copy and reference the top definition.

    Related topics

    • Next: Merging Block Constraints

    • STA: Generated Clocks and Dividers

    • STA: Interface Budgets

    Previous

    181 / 289

    Next: Merging Block Constraints

    Up next

    Up nextMerging Block Constraints

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Exceptions and Their Review