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

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • How to Use This Course
  • Reading Path
  • STA Glossary
  • STA Metrics Reference
  • STA Mastery Cheatsheet
  • Timing Paths and Checks16
  • Constraints & SDC Fundamentals20
  • Delay Graph & Path Analysis16
  • Beyond Setup and Hold16
  • Multi-Corner Multi-Mode Signoff16
  • OCV, AOCV, POCV and Variation16
  • Crosstalk and Noise16
  • ECO and Closure16
  • STA Debug Playbook16
  • STA Interview Prep26
  • Capstone Timing Signoff Lab4
    • Capstone Brief
    • Audit the Constraints
    • Classify and Fix
    • Signoff
Loading lesson

‹Capstone Timing Signoff Lab
  1. Topics
  2. Static Timing Analysis
  3. Capstone Timing Signoff Lab
  4. Classify and Fix

Part 11 · Capstone Timing Signoff Lab · All levels

Classify and Fix

Delete the overbroad exception, fix the shared-segment setup violation, and fix the hold violation with a delay sized to its setup slack.

On this page

    With the audit done, the constraint file is corrected and the real violations are visible. This lesson classifies each one, picks the method, and shows why the obvious hold fix is wrong while the right-sized one works.

    The corrected constraints and the real report

    Diagram
    # not-run: SDC is not executable here
    create_clock -name clk   -period 1.0 [get_ports clk]
    create_clock -name clk_b -period 1.3 [get_ports clk_b]
    create_generated_clock -name clk_div2 -source [get_ports clk] -divide_by 2 [get_pins u_div/q_reg/Q]
    set_false_path -from [get_clocks clk]   -to [get_clocks clk_b]
    set_false_path -from [get_clocks clk_b] -to [get_clocks clk]
    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
    BUDGET = {("clk", "clk"): 1.0, ("clk", "clk_div2"): 1.0, ("clk_div2", "clk"): 1.0,
              ("clk", "clk_b"): 0.3, ("clk_b", "clk"): 0.3}    # capture-edge budget per clock pair, ns (async pairs: arbitrary pessimistic placeholder, not the closest-edge gap)
    def path(l, c, mx, mn, sk=0.0):
        return {"l": l, "c": c, "mx": mx, "mn": mn, "su": 0.06, "ho": 0.05, "sk": sk}
    PATHS = {"E1": path("clk", "clk", 0.82, 0.20, 0.02), "E2": path("clk", "clk", 0.91, 0.16),
             "E3": path("clk", "clk", 0.90, 0.12, -0.03), "E4": path("clk", "clk_div2", 0.75, 0.30),
             "E5": path("clk_div2", "clk", 1.02, 0.30), "E6": path("clk", "clk_b", 0.40, 0.10),
             "E7": path("clk_b", "clk", 0.55, 0.10), "E8": path("clk", "clk", 0.90, 0.06, 0.05)}
    FP_ASYNC = {("clk", "clk_b"), ("clk_b", "clk")}              # false paths between the truly asynchronous clocks
    FP_DIV = {("clk_div2", "clk"), ("clk", "clk_div2")}          # false paths between the divided clock and its source
    def analyse(paths, clocks, exceptions):
        out = {}
        for name, p in paths.items():
            if p["l"] not in clocks or p["c"] not in clocks:
                out[name] = "unconstrained"
            elif any((p["l"], p["c"]) in ex for ex in exceptions):
                out[name] = "excepted"
            else:
                setup = BUDGET[(p["l"], p["c"])] + p["sk"] - p["su"] - p["mx"]
                hold = p["mn"] - (p["ho"] + p["sk"])
                out[name] = (round(setup, 3), round(hold, 3))
        return out
    def summary(res):
        nums = {k: v for k, v in res.items() if isinstance(v, tuple)}
        kinds = [v if isinstance(v, str) else "checked" for v in res.values()]
        return {"checked": kinds.count("checked"), "excepted": kinds.count("excepted"), "unconstrained": kinds.count("unconstrained"),
                "wns_setup": min(v[0] for v in nums.values()), "wns_hold": min(v[1] for v in nums.values())}
    clocks = {"clk", "clk_b", "clk_div2"}
    res = analyse(PATHS, clocks, [FP_ASYNC])
    s = summary(res)
    assert res["E4"] == (0.19, 0.25) and res["E5"] == (-0.08, 0.25) and res["E8"] == (0.09, -0.04)
    assert s == {"checked": 6, "excepted": 2, "unconstrained": 0, "wns_setup": -0.08, "wns_hold": -0.04}
    print("PASS: corrected constraints", s)

    Finding

    Check

    Slack (ns)

    Class

    E5, clk_div2 to clk

    Setup

    -0.080

    Real violation exposed by the corrected constraints

    E8, clk to clk

    Hold

    -0.040

    Real violation, present from the start

    E6, E7

    Excepted

    n/a

    Correct exceptions on truly asynchronous crossings (a CDC check owns them)

    Fixing E5: setup on a related path

    E4 and E5 both cross between the fast clock and the divided clock, so each gets the closest-edge relationship of 1.0 ns rather than the full divided period. The E5 data path takes 1.02 ns against a budget of 1.0 - 0.06 = 0.94 ns. A sizing change on a shared cell that removes 0.10 ns from the path clears it. Check hold at the same endpoint afterwards: the speed-up also shortens the minimum path, here from 0.30 to 0.25 ns.

    Fixing E8: hold, and why the obvious fix fails

    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
    BUDGET = {("clk", "clk"): 1.0, ("clk", "clk_div2"): 1.0, ("clk_div2", "clk"): 1.0,
              ("clk", "clk_b"): 0.3, ("clk_b", "clk"): 0.3}    # capture-edge budget per clock pair, ns (async pairs: arbitrary pessimistic placeholder, not the closest-edge gap)
    def path(l, c, mx, mn, sk=0.0):
        return {"l": l, "c": c, "mx": mx, "mn": mn, "su": 0.06, "ho": 0.05, "sk": sk}
    PATHS = {"E1": path("clk", "clk", 0.82, 0.20, 0.02), "E2": path("clk", "clk", 0.91, 0.16),
             "E3": path("clk", "clk", 0.90, 0.12, -0.03), "E4": path("clk", "clk_div2", 0.75, 0.30),
             "E5": path("clk_div2", "clk", 1.02, 0.30), "E6": path("clk", "clk_b", 0.40, 0.10),
             "E7": path("clk_b", "clk", 0.55, 0.10), "E8": path("clk", "clk", 0.90, 0.06, 0.05)}
    FP_ASYNC = {("clk", "clk_b"), ("clk_b", "clk")}              # false paths between the truly asynchronous clocks
    FP_DIV = {("clk_div2", "clk"), ("clk", "clk_div2")}          # false paths between the divided clock and its source
    def analyse(paths, clocks, exceptions):
        out = {}
        for name, p in paths.items():
            if p["l"] not in clocks or p["c"] not in clocks:
                out[name] = "unconstrained"
            elif any((p["l"], p["c"]) in ex for ex in exceptions):
                out[name] = "excepted"
            else:
                setup = BUDGET[(p["l"], p["c"])] + p["sk"] - p["su"] - p["mx"]
                hold = p["mn"] - (p["ho"] + p["sk"])
                out[name] = (round(setup, 3), round(hold, 3))
        return out
    def summary(res):
        nums = {k: v for k, v in res.items() if isinstance(v, tuple)}
        kinds = [v if isinstance(v, str) else "checked" for v in res.values()]
        return {"checked": kinds.count("checked"), "excepted": kinds.count("excepted"), "unconstrained": kinds.count("unconstrained"),
                "wns_setup": min(v[0] for v in nums.values()), "wns_hold": min(v[1] for v in nums.values())}
    m = 0.005
    e8 = PATHS["E8"]
    setup0 = BUDGET[("clk", "clk")] + e8["sk"] - e8["su"] - e8["mx"]
    hold0 = e8["mn"] - (e8["ho"] + e8["sk"])
    assert (round(setup0, 3), round(hold0, 3)) == (0.09, -0.04)
    assert round(setup0 + hold0, 3) == 0.05 and setup0 + hold0 >= 2 * m          # delay insertion can work in principle
    need = m - hold0
    assert round(need, 3) == 0.045
    def after(delay):
        return round(setup0 - delay, 3), round(hold0 + delay, 3)
    naive = after(0.10)                  # one coarse delay cell
    sized = after(0.05)                  # two small cells, sized to the window
    assert naive == (-0.01, 0.06) and sized == (0.04, 0.01)
    assert naive[0] < 0 and sized[0] >= m and sized[1] >= m
    print("PASS: window for the delay is", (round(need, 3), round(setup0 - m, 3)), "; naive 0.10 ns ->", naive, "; sized 0.05 ns ->", sized)

    Option

    Delay added

    Setup slack

    Hold slack

    Verdict

    None

    0

    +0.090

    -0.040

    Hold violation

    One coarse delay cell

    0.10

    -0.010

    +0.060

    Hold fixed, setup now fails

    Sized to the window

    0.05

    +0.040

    +0.010

    Both pass with at least the 5 ps margin

    The setup slack (0.090) plus the hold slack (-0.040) is +0.050 ns, so delay insertion can work. The delay must lie between 0.045 ns (to reach the margin on hold) and 0.085 ns (to keep the margin on setup). The coarse 0.10 ns cell overshoots the window and trades a hold violation for a setup one: the classic fix-breaks-fix. A 0.05 ns delay, built from two small cells, sits inside the window with 5 ps to spare on hold and 35 ps on setup. Insert it at the endpoint so that other paths are untouched, and check it in the slow corner, where the added delay is larger.

    Verification plan

    Change

    Rung of the ECO ladder

    Rerun

    Delete the false path on clk_div2

    Constraint change

    Full constraint audit and timing in all scenarios

    Size a shared cell for E5

    Cell swap or resize

    Timing, SI on affected nets, power

    Insert delay at E8

    Buffer insertion

    Timing, SI, extraction, DRC, power, and the slow-corner setup check

    Common pitfall

    Choosing the delay by what is available in the library rather than by the window. The window comes first; the cell is picked to fit it.

    Interview

    Tell the E8 story: sum of slacks first, then the window, then the cell.

    Check yourself What would the window be if E8's setup slack were only 0.030 ns?

    The sum is -0.010 ns, below the 0.010 ns needed for margins, so delay insertion cannot work: the endpoint would need a structural fix.

    Related topics

    • Back: Audit the Constraints

    • Next: Signoff

    • Fixing Hold Safely

    Previous

    182 / 183

    Next: Signoff

    Up next

    Up nextSignoff

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Capstone Brief