During a constraint refactor the team merges constraint snippets from an earlier project. The next signoff run shows WNS improving from -0.050 ns to +0.011 ns with no design change, and the team is pleased. Months later, bring-up reports an intermittent failure, roughly once an hour at high temperature, on a path between a clock and its divided version. The reports said the crossing was clean.
Fact | Value |
|---|---|
WNS before the refactor | -0.050 ns |
WNS after | +0.011 ns, with no RTL change |
Clocks involved | clk_a, and clk_b (a divide-by-2 generated from clk_a) |
Silicon symptom | Intermittent data error, hot, about once an hour |
A large improvement from constraints alone: what exactly changed in the constraints?
Which exceptions apply to the failing crossing, and how many paths does each cover?
Are clk_a and clk_b really unrelated? One is generated from the other.
wns_before, wns_after = -0.050, 0.011
gain = round(wns_after - wns_before, 3)
assert gain == 0.061
# the heuristic from the debug playbook: a constraint-only gain above 0.03 ns needs review
threshold = 0.03
assert gain > threshold
print("PASS: constraint-only gain", gain, "ns exceeds the review threshold", threshold, "ns")A 61 ps improvement with no design change should have been a flag, not a celebration. The review threshold in the debug playbook (a constraint-only gain above 0.03 ns) would have caught it. The second flag is physical: a divided clock is derived from its source, so the two are related, and an exception that treats them as asynchronous contradicts the structure of the design.
Its edges are a subset of the source's edges, so the two are synchronous and every path between them has a defined timing relationship that should be checked.