The CPU reads SRAM in 93.75 ns, and that total crosses the asynchronous bridge, the fabric and the memory wrapper. If the integrator hands the whole number to the project as one allowance, each block assumes another will absorb the synchroniser cost, and the path misses by exactly the crossing nobody owned. Split it instead: so many cycles per block, in each block's own clock, with the crossings as explicit rows.
A cross-block budget is a table, not a number. Each row spends cycles of one named clock; rows convert to nanoseconds once, at the end, using that clock's period. Crossings are rows too: three system clocks for a bridge traversal, two destination clocks per synchroniser. The NanoSoC fetch below shows the shape: seven system clocks plus four cpu clocks, and the periods 10.4167 ns and 5.2083 ns turn them into 93.75 ns. The block budgets fall out directly: the bridge owns its three-plus-three clocks, the fabric owns address plus data, the memory owns its two.
Path | Cycle budget | Total |
|---|---|---|
CPU single-word read (SRAM/ROM) | 1 cpu + 3 sys + 1 + 2 + 1 sys + 3 cpu | 7 sys + 4 cpu = 93.75 ns |
CPU 4-beat line fill | First word 18 cpu, then 3 beats at 2 cpu each | 24 cpu = 125 ns |
CPU read of DMA/SBR register | 3 + 1 + 1 + 3 + 1 + 1 sys, plus 4 cpu | 24 cpu = 125 ns |
CPU read of slow peripheral register | 9 sys + 4 per + 4 cpu | 281.25 ns (54 cpu) |
Interrupt to CPU | 2 per + 1 per + 2 cpu | 135.4 ns best, 182.3 ns worst |
These are latency budgets: cycle counts for a blocking transaction (what the software waits for), summed along a round trip. They are not setup-time budgets. A setup budget covers one path between two registers that must fit inside one clock period, for example a 10.417 ns clk_sys path split between the master side, the fabric and the slave side (an illustrative 25%, 50% and 25%, which is 2.604, 5.208 and 2.604 ns). The 93.75 ns is never a setup allowance for any single path: every register-to-register leg inside it still has to close in its own clock period. That second kind of budget belongs to the interface budgets in the STA course and is shown in the calculator below only as a contrast.
The script below re-adds the single-word read from per-block rows and checks it against the NanoSoC total, then does the same for the peripheral read with its three clocks. Two paths, one method: rows in cycles, one conversion at the end.
T_cpu, T_sys, T_per = 1/192e6, 1/96e6, 1/24e6
fetch = {"issue_cpu": 1*T_cpu, "xbar_addr_sys": 1*T_sys, "req_xing_sys": 3*T_sys,
"mem_sys": 2*T_sys, "xbar_data_sys": 1*T_sys, "resp_xing_cpu": 3*T_cpu}
total = sum(fetch.values())
assert abs(total - 93.75e-9) < 0.01e-9, total
periph = 9*T_sys + 4*T_per + 4*T_cpu
assert abs(periph - 281.25e-9) < 0.01e-9, periph
linefill = 18*T_cpu + 3*2*T_cpu
assert abs(linefill - 125e-9) < 0.01e-9, linefill
# Wrong-clock check: three peripheral clocks priced at the system period.
right, wrong = 3*T_per, 3*T_sys
assert abs(right - 125e-9) < 0.01e-9 and abs(wrong - 31.25e-9) < 0.01e-9
assert abs((right - wrong) - 93.75e-9) < 0.01e-9
# A different kind of budget: setup inside one clk_sys period (illustrative split).
share = {"master side": 0.25, "fabric": 0.50, "slave side": 0.25}
assert abs(sum(share.values()) - 1.0) < 1e-12
setup_rows = {k: v * T_sys * 1e9 for k, v in share.items()}
assert abs(sum(setup_rows.values()) - 10.4167) < 0.001
assert setup_rows["fabric"] < total * 1e9 # a setup row is not a latency row
print(f"PASS: fetch {total*1e9:.2f} ns, periph {periph*1e9:.2f} ns, linefill {linefill*1e9:.0f} ns, wrong clock understates by 93.75 ns")Every cross-block path has a row-per-block table with named clocks, not a single nanosecond allowance.
Crossing rows are explicit and owned by the bridge, never absorbed silently into a neighbour.
Each block's rows sum to no more than its handed budget; the top keeps the remainder.
Check any handed budget by re-adding its rows in the block's own clock before converting: cycle arithmetic catches a mistyped period that nanosecond arithmetic hides. The classic error is converting in the wrong clock — three peripheral clocks priced at the system period understate a bridge by 93.75 ns (125 ns versus 31.25 ns). Name the clock on every row and the class of error disappears.
Mind the ratio trap hidden in the peripheral row: nine system clocks plus four peripheral clocks plus four cpu clocks mix three periods in one total, and a 2:1 or 4:1 ratio change revises the conversion silently. Keep the rows in cycles and the periods in one table, and the 281.25 ns recomputes itself when the ratio moves; hard-code the nanoseconds and the budget rots at the first frequency change. The line-fill row shows the same discipline at its simplest: 18 plus 3 times 2 cpu cycles, one clock, one conversion.
Because its clock differs from its neighbour's: cycles in the wrong clock convert wrongly. Hand each block cycles in its own clock plus the shared conversion table, and keep one conversion in one place.