The last step assembles the evidence. The final table comes from rerunning the engine with all fixes applied; the checklist uses the blocker and major weights from the debug playbook; the dashboard compares the state at handover with the state now; and the statement says exactly what is claimed and what it depends on.
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"}
PATHS["E5"] = dict(PATHS["E5"], mx=1.02 - 0.10, mn=0.30 - 0.05) # shared-cell sizing: 0.10 ns faster
PATHS["E8"] = dict(PATHS["E8"], mx=0.90 + 0.05, mn=0.06 + 0.05) # 0.05 ns of delay at the endpoint
res = analyse(PATHS, clocks, [FP_ASYNC])
s = summary(res)
margin = 0.005
assert res == {"E1": (0.14, 0.13), "E2": (0.03, 0.11), "E3": (0.01, 0.1), "E4": (0.19, 0.25), "E5": (0.02, 0.2),
"E6": "excepted", "E7": "excepted", "E8": (0.04, 0.01)}
assert s == {"checked": 6, "excepted": 2, "unconstrained": 0, "wns_setup": 0.01, "wns_hold": 0.01}
assert all(v[0] >= margin and v[1] >= margin for v in res.values() if isinstance(v, tuple))
print("PASS: final", s)Endpoint | Setup (ns) | Hold (ns) | Note |
|---|---|---|---|
E1 | +0.140 | +0.130 | |
E2 | +0.030 | +0.110 | |
E3 | +0.010 | +0.100 | Smallest setup slack: watch after any change |
E4 | +0.190 | +0.250 | Newly constrained |
E5 | +0.020 | +0.200 | Fixed by sizing a shared cell |
E6, E7 | excepted | excepted | Asynchronous crossings, owned by the CDC signoff |
E8 | +0.040 | +0.010 | Fixed by a 0.05 ns delay inside the computed window |
Every checked endpoint now has at least the 5 ps margin on both checks: setup WNS +0.010 ns (E3) and hold WNS +0.010 ns (E8). The two excepted endpoints are covered by the CDC methodology, not by timing, and that dependency belongs in the statement.
Measure | At handover | Final |
|---|---|---|
Endpoints actually checked | 4 of 8 | 6 of 8 (the other 2 excepted with evidence) |
Unconstrained endpoints | 2 | 0 |
Exceptions | 4 commands (2 pairs; one pair wrong) | 2 commands (1 pair, covers exactly 2 crossings) |
Setup WNS | +0.010 ns (misleading) | +0.010 ns (all 6 checked endpoints) |
Hold WNS | -0.040 ns | +0.010 ns |
Real violations found and fixed | 1 known (E8 hold) | 2 fixed (E5 setup, newly exposed; E8 hold, known) |
items = [
("no unclocked or unconstrained endpoints", "blocker", "closed"),
("every exception reviewed with its path count", "blocker", "closed"),
("setup and hold clean with margin in the analysed scenario", "blocker", "closed"),
("hold fixes fit inside their setup slack", "blocker", "closed"),
("analysed scenario only; other scenarios not run", "blocker", "open"),
("asynchronous crossings covered by the CDC signoff", "major", "waived"),
("run manifest recorded", "major", "closed"),
]
blockers_open = [n for n, w, s in items if w == "blocker" and s != "closed"]
majors_open = [n for n, w, s in items if w == "major" and s == "open"]
waived = [n for n, w, s in items if s == "waived"]
assert blockers_open == ["analysed scenario only; other scenarios not run"] and majors_open == [] and waived == ["asynchronous crossings covered by the CDC signoff"]
ready = not blockers_open and not majors_open
assert ready is False
print("PASS: not ready; open blockers", blockers_open, "; waived", waived)The engine models a single scenario, so the other-scenarios item is honestly open: the capstone numbers are one corner. The crossings E6 and E7 are waived from timing on the strength of the CDC signoff, and the waiver is listed rather than counted as fixed. With a blocker still open, the result is not ready and the correct statement refuses the signoff.
Timing is not signed off. The analysed scenario is clean: all six timed endpoints meet setup and hold with at least 5 ps of margin (setup WNS +0.010 ns at E3, hold WNS +0.010 ns at E8). This analysis uses one scenario and excludes clock uncertainty, derates, common path pessimism removal and SI; sign-off requires rerunning the slow and fast corners with those included. Constraints were audited: no unclocked endpoints, one exception remaining, which covers exactly the two asynchronous crossings E6 and E7 and depends on their CDC signoff. Two real violations were fixed: E5 by sizing a shared cell, E8 by a 0.05 ns delay inside its computed window. Any change to the clocks, exceptions, or the E5 and E8 fixes re-opens the affected rows.
The final table (the engine reproduces it), the exception audit (E6 and E7 are the only paths covered), and the fix windows (0.045 to 0.085 ns for E8, with 0.05 ns chosen).
Extend it: raise the clk_div2 data delay at E4 to 0.95 ns and rerun. Which check fails, by how much, and which fix method fits? Then change E8's maximum delay to 0.96 ns and decide whether delay insertion can still fix it by computing the sum of its two slacks.