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. Capstone Brief

Part 11 · Capstone Timing Signoff Lab · All levels

Capstone Brief

The block, its eight endpoints, the constraint file as received, and a mini timing engine that reports a clean-looking result.

On this page

    The capstone is a paper signoff of a small block. A constraint file has been handed over with a timing report that looks nearly clean. Your job is to find out what the report does not tell you, fix what is really wrong, and write a signoff statement a reviewer can check. Three defects are planted, and the numbers are small enough that a runnable engine of about thirty lines computes every slack. Everything here is illustrative.

    The block

    A block runs on a 1.0 ns clock clk. A divider produces clk_div2 (period 2.0 ns) for a slow register bank. A second clock clk_b (period 1.3 ns) is truly asynchronous and enters through synchronisers. Eight endpoints matter. Setup time is 0.06 ns and hold time 0.05 ns everywhere; sk is the capture clock latency minus the launch clock latency.

    Endpoint

    Launch to capture

    Max delay (ns)

    Min delay (ns)

    Skew sk (ns)

    E1

    clk to clk

    0.82

    0.20

    +0.02

    E2

    clk to clk

    0.91

    0.16

    0.00

    E3

    clk to clk

    0.90

    0.12

    -0.03

    E4

    clk to clk_div2

    0.75

    0.30

    0.00

    E5

    clk_div2 to clk

    1.02

    0.30

    0.00

    E6

    clk to clk_b (synchroniser input)

    0.40

    0.10

    0.00

    E7

    clk_b to clk (synchroniser input)

    0.55

    0.10

    0.00

    E8

    clk to clk

    0.90

    0.06

    +0.05

    The constraint file as received

    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]
    set_false_path -from [get_clocks clk]   -to [get_clocks clk_b]
    set_false_path -from [get_clocks clk_b] -to [get_clocks clk]
    set_false_path -from [get_clocks clk_div2] -to [get_clocks clk]
    set_false_path -from [get_clocks clk]      -to [get_clocks clk_div2]

    The engine and the first report

    The engine computes setup slack as the capture-edge budget plus skew minus setup time minus the maximum delay, and hold slack as the minimum delay minus hold time minus skew. A pair of unrelated clocks gets an arbitrary pessimistic placeholder budget of 0.3 ns, not the real closest-edge gap; an endpoint on a clock that is not defined is unconstrained; a path covered by a false path is excepted.

    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
    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())}
    res = analyse(PATHS, {"clk", "clk_b"}, [FP_ASYNC, FP_DIV])        # as received: clk_div2 is not defined
    s = summary(res)
    assert res["E1"] == (0.14, 0.13) and res["E3"] == (0.01, 0.1) and res["E8"] == (0.09, -0.04)
    assert res["E4"] == "unconstrained" and res["E5"] == "unconstrained"
    assert res["E6"] == "excepted" and res["E7"] == "excepted"
    assert s == {"checked": 4, "excepted": 2, "unconstrained": 2, "wns_setup": 0.01, "wns_hold": -0.04}
    print("PASS: as received", s)

    Measure

    As received

    Endpoints checked

    4 of 8

    Excepted by false paths

    2 (E6, E7)

    Unconstrained

    2 (E4, E5)

    Setup WNS

    +0.010 ns (E3)

    Hold WNS

    -0.040 ns (E8)

    Setup looks clean at +0.010 ns and the only visible violation is a 40 ps hold failure at E8. Before reading on, predict what is wrong: how many of the eight endpoints are really being checked, what do the four false-path lines cover, and is the hold fix at E8 as simple as it looks?

    Your tasks

    • Audit the constraints: coverage and exceptions (next lesson).

    • Classify what the audit finds and fix it with the right method (lesson three).

    • Assemble the signoff: final table, checklist, statement (lesson four).

    Warning

    Do not trust the first table. Four of eight endpoints are checked and the 'clean' setup number covers only those four.

    Check yourself Why is the setup WNS of +0.010 ns not evidence that setup is fine?

    Two endpoints have no check at all and two are excepted, so the number describes only four of the eight.

    Related topics

    • Next: Audit the Constraints

    • Constraint Sanity Checks

    Previous

    180 / 183

    Next: Audit the Constraints

    Up next

    Up nextAudit the Constraints

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Classify and Fix
    • Signoff