Latch timing is easy to get subtly wrong because reports show borrowing, not a single slack. A short set of rules makes the review mechanical.
Rule | Why |
|---|---|
Every latch is intentional and documented | Accidental latches from RTL are errors; lint finds them |
Clock duty cycle is defined for latch clocks | The transparent window depends on it |
Borrowing is bounded and reported per latch | No latch should borrow nearly its whole window without review |
Hold is checked at the correct edge for each pair of phases | Same-phase latches have a transparent overlap that can race |
End-to-end slack is reported per chain | Single-stage slack hides chain failures |
Paths through transparent latches are in the timing report | A tool that stops at the latch hides the downstream path |
latches = {"L1": 1.0, "L2": 4.7, "L3": 0.2} # borrowed ns
window, setup = 5.0, 0.1
limit = 0.9 * (window - setup) # review threshold: 90% of the maximum borrow
flag = [n for n, b in latches.items() if b > limit]
assert abs(limit - 4.41) < 1e-9 and flag == ["L2"]
print("PASS: latches over the review threshold:", flag)A simple review threshold (90% of the maximum possible borrow) flags L2, which borrows 4.7 of a possible 4.9 ns: almost no setup margin remains, and any small delay increase would fail the latch. The threshold is a team policy; the principle is that borrowing near the maximum is a risk even when slack is technically positive.
A table per block of latches, their borrowed time, maximum borrow and flagged ones.
Hold checks between phases are reviewed (opposite phases have more margin but still need a hold check; same-phase transparent pairs are the highest race risk).
Latch-based structures are covered by constraints and checks the team understands; unexplained latches are removed.
A hold race: both are transparent at the same time, so data can race through both within one phase. Hold must be checked at the correct edges and often fixed with delay or by using opposite phases.