A crossing missing from the inventory is a crossing with no synchroniser, no constraint and no test. The inventory is therefore the most safety-critical table in clock planning: ten rows that every tool and every test derives from. This lesson writes all ten with their mechanisms, then works the interrupt path end to end in nanoseconds so the table carries at least one latency the reader can feel. Its one job is the inventory; choosing mechanisms for new crossings is next.
The ten groups fall into four kinds. The AXI bridge crosses X1 and X2 carry whole channels through 4-deep dual-clock FIFOs with gray-coded pointers. The APB bridge crosses X3 and X4 carry address, data and response through a request-and-acknowledge handshake that holds data stable. The single-bit groups X5 to X8 and X10 cross as levels through two-flop synchronisers: interrupt lines, reset releases, lock flags and power sequencing. Group X9 is the outside world: GPIO, UART receive and SPI data synchronised per input before use. The table is quoted from the NanoSoC specification:
# | From | To | Signals | Mechanism |
|---|---|---|---|---|
X1 | clk_cpu | clk_sys | AXI AW, W, AR requests | Dual-clock FIFO per channel, 4 deep |
X2 | clk_sys | clk_cpu | AXI B, R responses | Dual-clock FIFO per channel, 4 deep |
X3 | clk_sys | clk_per | APB request: address, data, control | Req toggle synchronised, data held stable |
X4 | clk_per | clk_sys | APB response: read data, error, ack | Ack toggle synchronised, data held stable |
X5 | clk_sys | clk_per | irq_dma, irq_sbr | 2-flop level synchroniser in the controller |
X6 | clk_per | clk_cpu | irq_cpu controller output | 2-flop level synchroniser at the CPU |
X7 | clk_per | clk_sys | Reset release, pll_lock, clock enables | Per-domain sync release; 2-flop for lock |
X8 | clk_per | clk_cpu | Reset release | Per-domain sync release |
X9 | Pads | clk_per | GPIO, UART RX, SPI MISO | 2-flop synchroniser per input |
X10 | clk_per | clk_sys | pwr_up_done, pgood | 2-flop synchroniser |
The X5 plus X6 chain has a quoted latency in the NanoSoC specification, best 135.4 ns and worst 182.3 ns, and the script recomputes both from the synchroniser depths so the numbers are never quoted on faith:
T_CPU, T_PER = 1e9 / 192e6, 1e9 / 24e6 # 5.2083 ns, 41.6667 ns
# Best case: 2-flop sync into clk_per, 1 controller register, 2-flop into cpu.
best = 2 * T_PER + 1 * T_PER + 2 * T_CPU
assert abs(best - 135.4167) < 0.01, best
# Worst case adds one cycle of alignment at each synchroniser input.
worst = best + T_PER + T_CPU
assert abs(worst - 182.2917) < 0.01, worst
# In cpu cycles: 26 best, 35 worst, matching the NanoSoC specification.
assert round(best / T_CPU) == 26, best / T_CPU
assert round(worst / T_CPU) == 35, worst / T_CPU
print("PASS: irq path 135.4 ns best (26 cpu cycles), 182.3 ns worst (35)")The ten rows above, current, with no eleventh signal found by the CDC tool unlisted.
Each row names the synchroniser or FIFO instance that implements it, not just the mechanism class.
Level inputs state their minimum hold: at least 2 destination cycles, 83.3 ns into clk_per.
Run the structural CDC check against this table: every crossing the tool finds must match a row, and every row must match circuitry. Sweep the clock ratios in simulation with CDC stimulus (ratio and phase sweeps) and confirm no pulse shorter than the hold requirement crosses X5 or X6. A missed interrupt that correlates with ratio or temperature points at hold time, not at the controller.
The X5 synchroniser needs the value stable for at least 2 clk_per cycles. The rule is a level of at least 2 clk_per (83.3 ns): a pulse under 41.7 ns, one clk_per, can be missed completely, and anything under 83.3 ns is not guaranteed to give a clean sample after the first flop. The DMA holds irq high until software clears DONE or ERR, so the level is always long enough.