Crossings close not when the RTL is written but when the review can answer five questions with evidence: is the inventory complete, is each mechanism right, do the constraints match, were the ratios swept, and is signoff clean. An unchecked crossing is a field failure with a long fuse, so the checklist is explicit and the evidence is attached, not promised. This lesson is that checklist. Its one job is the review; everything it checks was taught in the two previous lessons.
First, inventory: the CDC tool's found crossings diffed against the ten rows, with any extra row explained and any missing row convicted as a tool-setup bug. Second, mechanism: each row's class-to-mechanism mapping restated, with depth arithmetic for the FIFOs and hold times for the levels. Third, constraints: the pair is declared asynchronous in the constraints (one clock-group declaration), the only extra constraints are the bounded-delay and skew limits on FIFO pointers and handshake data, and no other false path or multicycle crosses the pair; the exception audit from the switching lesson is rerun here. Fourth, ratio sweeps: each crossing simulated at the worst phase alignment, because NanoSoC ratios are fixed but phase is not. Fifth, signoff: the CDC methodology run clean with waivers that carry evidence and expiry, following CDC Signoff Methodology, and reset-domain crossing checked alongside, since reset releases cross these same boundaries.
# Audit model: the inventory every review re-runs. Each row has from, to and
# a mechanism, and the mechanism must match the signal class.
DOMAINS = {"clk_cpu", "clk_sys", "clk_per", "pad"}
INVENTORY = [
("X1", "clk_cpu", "clk_sys", "fifo", "multi"),
("X2", "clk_sys", "clk_cpu", "fifo", "multi"),
("X3", "clk_sys", "clk_per", "handshake", "multi"),
("X4", "clk_per", "clk_sys", "handshake", "multi"),
("X5", "clk_sys", "clk_per", "level-2flop", "single"),
("X6", "clk_per", "clk_cpu", "level-2flop", "single"),
("X7", "clk_per", "clk_sys", "level-2flop", "single"),
("X8", "clk_per", "clk_cpu", "level-2flop", "single"),
("X9", "pad", "clk_per", "level-2flop", "single"),
("X10", "clk_per", "clk_sys", "level-2flop", "single"),
]
OK = {"multi": {"fifo", "handshake"}, "single": {"level-2flop"}}
def audit(rows):
errors = []
if len({r[0] for r in rows}) != len(rows):
errors.append("duplicate crossing id")
for x, src, dst, mech, cls in rows:
if src not in DOMAINS or dst not in DOMAINS:
errors.append(x + ": clock not in the domain table")
if src == dst:
errors.append(x + ": not a crossing")
if mech not in OK[cls]:
errors.append(x + ": " + mech + " is wrong for a " + cls + "-bit signal")
return errors
assert audit(INVENTORY) == []
kinds = [m for _, _, _, m, _ in INVENTORY]
assert (kinds.count("fifo"), kinds.count("handshake"), kinds.count("level-2flop")) == (2, 2, 6)
assert len(INVENTORY) == 10
# The audit can fail: a bus on two flops, an unknown clock, a same-clock row.
assert audit([("X1", "clk_cpu", "clk_sys", "level-2flop", "multi")]) != []
assert audit([("X1", "clk_cpu", "clk_dma", "fifo", "multi")]) != []
assert audit([("X1", "clk_sys", "clk_sys", "fifo", "multi")]) != []
print("PASS: 10 crossing groups, mechanisms match signal class; 3 bad rows rejected")The tool-versus-table diff with zero unexplained rows, attached to the review record.
The FIFO depth lines, the level hold lines and the exception audit, each current.
A clean signoff report or waivers with evidence and expiry; a waiver without either is an open item, not a pass.
Re-run the audit after every hierarchy or constraint change; crossings rot when constraints are edited without it. Treat a late-appearing eleventh crossing as a process failure, not a circuit problem: find which edit added the signal and why the inventory did not move with it, then fix the process that allowed the split.
No. A clean report against a stale setup proves the wrong design. Update the table, rerun the audit and the tool, and only then claim signoff.