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
    • Graph Traversal & Slack
      • Arrival and Required Times
      • Forward and Backward Propagation
      • Slack Worked Example
      • A Tiny Timing Engine
    • Cell vs Net Delay Budgets
    • Path Group Prioritization
    • Report Timing Deep Read
  • 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
Loading lesson

‹Graph Traversal & Slack
  1. Topics
  2. Static Timing Analysis
  3. Delay Graph & Path Analysis
  4. Graph Traversal & Slack
  5. A Tiny Timing Engine

Part 3 · Delay Graph and Path Analysis · All levels

A Tiny Timing Engine

A complete setup timing engine in a few dozen lines of Python: propagate arrivals and required times, report slack and the worst path.

On this page

    Why it matters

    You do not fully understand a timing report until you have built a toy that produces one. This engine takes a graph with delays, a clock period and a setup time, and prints the worst path and per-endpoint slack. It deliberately omits everything else (slews, corners, hold) so the core algorithm is visible.

    The engine

    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
    from collections import defaultdict
    def analyse(edges, starts, ends, period, setup, start_arrival=0.0):
        succ, pred = defaultdict(list), defaultdict(list)
        nodes = set(starts) | set(ends)
        for (a, b), w in edges.items():
            succ[a].append((b, w)); pred[b].append((a, w)); nodes |= {a, b}
        # topological order (Kahn)
        indeg = {n: len(pred[n]) for n in nodes}
        order, ready = [], [n for n in nodes if indeg[n] == 0]
        while ready:
            n = ready.pop(); order.append(n)
            for m, _ in succ[n]:
                indeg[m] -= 1
                if indeg[m] == 0: ready.append(m)
        arr, via = {}, {}
        for n in order:
            if n in starts: arr[n] = start_arrival
            elif pred[n]:
                arr[n], via[n] = max((arr[p] + w, p) for p, w in pred[n])
        req = {e: period - setup for e in ends}
        for n in reversed(order):
            if n not in req and succ[n]:
                req[n] = min(req[m] - w for m, w in succ[n])
        slack = {n: req[n] - arr[n] for n in order if n in req and n in arr}
        worst_end = min(ends, key=lambda e: slack[e])
        path, n = [worst_end], worst_end
        while n in via: n = via[n]; path.append(n)
        return arr, slack, worst_end, path[::-1]
    edges = {("ff1","u1"):0.40, ("u1","u2"):0.30, ("u2","ff2"):0.50, ("u1","u3"):0.55, ("u3","ff3"):0.60, ("ff4","u3"):0.20}
    ends = {"ff2", "ff3"}
    arr, slack, worst, path = analyse(edges, starts={"ff1","ff4"}, ends=ends, period=1.5, setup=0.10)
    assert abs(arr["ff3"] - 1.55) < 1e-9 and abs(slack["ff3"] + 0.15) < 1e-9
    assert worst == "ff3" and path == ["ff1", "u1", "u3", "ff3"]
    assert abs(slack["ff2"] - 0.20) < 1e-9
    print("PASS: worst endpoint", worst, "slack", round(slack[worst], 2), "path", " -> ".join(path), "per-endpoint slack", {e: round(slack[e], 2) for e in sorted(ends)})

    With a 1.5 ns period and 0.1 ns setup the required time is 1.40 ns. Endpoint ff2 arrives at 1.20 (slack +0.20); ff3 arrives at 1.55 through ff1-u1-u3 (slack -0.15), because the route from ff4 is shorter (0.80). The engine reports ff3 as worst and recovers the path by following the worst predecessor at each node. It reproduces a report only when launch and capture latencies and uncertainty are folded into its two parameters (start_arrival and period - setup); it cannot model different clocks per endpoint, and a node with no successor needs handling. Everything a real tool adds is refinement on top of this skeleton: delays that depend on slew and load, separate early and late values, clock paths, derates, exceptions.

    What to try

    • Change the period to 1.7 ns and watch both slacks become positive.

    • Add a second edge into u2 with a longer delay and see the arrival and the worst path change.

    • Compute a hold-style pass by replacing max with min and the required time by a hold bound.

    Common pitfall

    Believing a toy engine's numbers. It teaches the structure; real numbers need slew-dependent delays, proper clock modelling, corners and variation. Use it to understand reports, not to sign off.

    Interview

    Being able to sketch this algorithm (levelise, propagate arrival forward with max, propagate required backward with min, take slack, backtrack the worst path) is a strong answer to "how does STA work".

    Check yourself Why does the engine take max over predecessors when computing arrival?

    For setup the latest arriving signal determines when the data is stable, so the arrival at a node is the maximum over all routes into it. For hold the earliest arrival matters, so a hold pass would use min.

    Related topics

    • Slack Worked Example

    • The Timing Graph

    Previous

    45 / 183

    Next: Cell vs Net Delay Budgets

    Up next

    Up nextCell vs Net Delay Budgets

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Arrival and Required Times
    • Forward and Backward Propagation