Every violation costs effort and implementation resources: upsizing, buffering, rerouting. Margin that is not backed by real risk produces violations that need fixing for no gain. Counting how many endpoints flip as the derate rises makes the price visible.
base_slack = [0.30, 0.25, 0.22, 0.18, 0.15, 0.12, 0.10, 0.08, 0.05, 0.02] # nominal setup slack (ns)
delay = [1.4, 1.2, 1.5, 1.0, 1.3, 0.9, 1.1, 1.6, 1.0, 1.2] # data path delay (ns)
def failing(derate):
return sum(1 for s, d in zip(base_slack, delay) if s - d * (derate - 1.0) < -1e-9)
counts = {d: failing(d) for d in (1.00, 1.05, 1.10, 1.15)}
assert counts == {1.00: 0, 1.05: 1, 1.10: 4, 1.15: 7}
extra = counts[1.10] - counts[1.05]
assert extra == 3
print("PASS: failing endpoints by late derate", counts, "; going from 1.05 to 1.10 adds", extra)Late derate | Failing endpoints (of 10) |
|---|---|
1.00 | 0 |
1.05 | 1 |
1.10 | 4 |
1.15 | 7 |
Ten endpoints with nominal slack between 0.02 and 0.30 ns: raising the derate from 1.05 to 1.10 turns three more endpoints into violations, and 1.15 fails seven of ten. The relationship is not linear because endpoints are not evenly spread: it depends on how many sit near zero slack. That is why the cost of a few percent of derate is best measured on the actual design, not estimated.
A sensitivity run: violations and TNS at the policy derate and at a step above and below.
Margin changes are justified against that sensitivity, with the cost of fixing the extra violations.
Endpoints that fail only at the highest derate are listed: they show where the margin is doing work.
The one with the smallest slack relative to its delay: the tenth endpoint (0.02 ns slack, 1.2 ns delay) fails already at 1.05 because 1.2 × 0.05 = 0.06 ns exceeds its slack.