A budget table that nobody can recompute is a rumour. The NanoSoC link table (in the NanoSoC tour) already shows its formulae, peak as width times clock and sustained as peak times burst efficiency, but formulae in prose drift from the numbers beside them. This lesson pins them together in one model: change a clock or a width and every dependent number must move, or the model, not the table, is wrong.
Peak on any link is width in bytes times clock in megahertz, giving megabytes per second directly. Sustained multiplies by burst efficiency, beats over beats plus the one bubble per burst. The APB rows replace the bubble with clocks per access: three clocks for the bus alone, five with the bridge's accept and respond, and 208.33 ns per access behind the apb_cdc. The model below builds the whole table as a function of the three clocks, compares every peak and sustained value against the published numbers with a relative tolerance (so the 0.0115 MB/s UART row is checked as strictly as the 722.8 MB/s row), and is then run with a changed clock to prove the comparison can fail.
# NanoSoC teaching values: the link table as a function of the three clocks.
def link_table(f_cpu=192.0, f_sys=96.0, f_per=24.0):
# name -> (peak MB/s, sustained MB/s)
def sus(peak, burst):
return peak * burst / (burst + 1)
cdc_ns = 2 * (1e3 / f_per) + 2 * (1e3 / f_per) + 2 * (1e3 / f_sys) + 2 * (1e3 / f_sys)
return {
"cpu_to_cdc": (4 * f_cpu, 16 / (24 * 1e3 / f_cpu) * 1e3), # one 4-beat line fill = 24 clk_cpu
"cdc_to_xbar": (4 * f_sys, sus(4 * f_sys, 16)),
"sbr_read": (4 * f_sys, sus(4 * f_sys, 16)),
"sbr_write": (4 * f_sys, sus(4 * f_sys, 16)),
"sram_bank": (4 * f_sys, sus(4 * f_sys, 16)),
"same_bank_each": (4 * f_sys, sus(4 * f_sys, 16) / 2),
"rom": (4 * f_sys, sus(4 * f_sys, 16)),
"both_banks": (2 * 4 * f_sys, 2 * sus(4 * f_sys, 16)),
"dma_in": (4 * f_sys, sus(4 * f_sys, 16)),
"dma_out": (4 * f_sys, sus(4 * f_sys, 16)),
"burst8": (4 * f_sys, sus(4 * f_sys, 8)),
"burst4": (4 * f_sys, sus(4 * f_sys, 4)),
"burst1": (4 * f_sys, sus(4 * f_sys, 1)),
"apb_fast": (4 * f_sys / 3, 4 * f_sys / 5), # 3 clocks bus, 5 with accept and respond
"apb_fast_zero_wait": (4 * f_sys / 2, 4 * f_sys / 4),
"apb_slow": (4 * f_per / 2, 4 / cdc_ns * 1e3), # 208.33 ns per access through the apb_cdc
"uart": (f_per * 1e6 / (16 * 13) / 10 / 1e6,) * 2, # line rate: 10 bits per byte
"spi": (f_per / (2 * (1 + 1)) / 8,) * 2, # 6 Mb/s line
}
EXPECT = {
"cpu_to_cdc": (768.0, 128.0), "cdc_to_xbar": (384.0, 361.4), "sbr_read": (384.0, 361.4),
"sbr_write": (384.0, 361.4), "sram_bank": (384.0, 361.4), "same_bank_each": (384.0, 180.7),
"rom": (384.0, 361.4), "both_banks": (768.0, 722.8), "dma_in": (384.0, 361.4),
"dma_out": (384.0, 361.4), "burst8": (384.0, 341.3), "burst4": (384.0, 307.2),
"burst1": (384.0, 192.0), "apb_fast": (128.0, 76.8), "apb_fast_zero_wait": (192.0, 96.0),
"apb_slow": (48.0, 19.2), "uart": (0.01154, 0.01154), "spi": (0.750, 0.750),
}
def mismatches(table):
bad = []
for name, want in EXPECT.items():
for got, w in zip(table[name], want):
if abs(got - w) > 1e-3 * w: # relative tolerance: tiny rows are checked as hard as big ones
bad.append(name)
break
return bad
assert mismatches(link_table()) == []
assert len(EXPECT) * 2 == 36 # 18 rows, peak and sustained
# the comparison can fail: a 100 MHz fabric moves every clk_sys row
assert "sbr_read" in mismatches(link_table(f_sys=100.0))
assert "uart" not in mismatches(link_table(f_sys=100.0)) # clk_per rows do not depend on clk_sys
# removing one bridge accept cycle: 4 x 96 / 4 and 4 B / (208.33 - 10.4167 ns)
assert abs(4 * 96 / 4 - 96.0) < 1e-9
assert abs(4 / (208.3333 - 10.41667) * 1e3 - 20.2) < 0.05
print("PASS: 36 link-table numbers reproduced, a changed clock is caught, one bridge cycle gives 96.0 and 20.2 MB/s")The model script is versioned with the map and the clocks: new divider, new run, new table.
Every row states the burst length or clocks-per-access its sustained number assumes, and peak and sustained are never mixed in one row.
Illustrative numbers, where the table is silent, are labelled and kept consistent across lessons.
Run the model before every simulation campaign and diff its output against the table in the specification: any drift means the spec or the clocks moved without the other. When a simulated link disagrees with its row, check the traffic shape first, burst lengths and idle gaps, exactly as the hub's check-yourself warns, before suspecting the RTL.
The with-bridge APB rows: the fast segment rises from 76.8 to 4 x 96 / 4 = 96.0 MB/s (128.0 needs both bridge clocks gone), and 19.2 becomes 4 B / 197.92 ns = 20.2 MB/s. The bus-only rows do not move.