Every NanoSoC path has a peak and a sustained rate in the link table, and the sustained rate is the one workloads must fit. Peak is width times frequency — 4 bytes at 96 MHz is 384.0 MB/s — and sustained is peak times burst efficiency, L over L + 1 for one bubble cycle per burst. A reader who can re-derive any row owns the table; a reader who quotes it blind inherits its assumptions.
Two penalties separate peak from sustained: arbitration bubbles on the fabric and bridge latency on the APB leg. Long bursts amortise the bubble — efficiency rises from 50% at length 1 to 94.1% at length 16 — which is why the fabric, the streams and the SRAMs all quote 361.4 MB/s sustained. The APB legs have no bursts to amortise with: the system-side segment spends 3 clocks per access on the bus plus 2 in the bridge (5 total, 76.8 MB/s), and the slow segment crosses clock domains for 208.33 ns per access (19.2 MB/s). The streams match the fabric at 361.4 because the DMA moves one word per clock when the source keeps up.
Link | Peak (MB/s) | Sustained (MB/s) | Why |
|---|---|---|---|
Fabric, SBR channels, SRAM, ROM | 384.0 | 361.4 | Burst 16 at 16/17 efficiency |
Same-bank shared streams | 384.0 total | 180.7 each | One port shared: 361.4 / 2 |
Both SRAM banks together | 768.0 | 722.8 | Two ports at 361.4 each |
System APB segment | 128.0 bus | 76.8 with bridge | 5 clocks per access |
Slow APB segment | 48.0 bus | 19.2 with bridge | 208.33 ns per access |
CPU line fill | n/a | 128.0 | 16 bytes in 125 ns |
The script re-derives the efficiency column and every sustained row it feeds, plus the two bridge rates.
for L, want in ((1, 50.0), (4, 80.0), (8, 88.9), (16, 94.1)):
eff = L / (L + 1) * 100
assert abs(eff - want) < 0.05, (L, eff)
assert abs(384.0 * L/(L+1) - (192.0, 307.2, 341.3, 361.4)[(1,4,8,16).index(L)]) < 0.1
assert abs(361.4 / 2 - 180.7) < 0.05
assert abs(2 * 361.4 - 722.8) < 0.05
assert abs(4*96/5 - 76.8) < 0.01 # APB system leg with bridge
assert abs(4/208.33e-9/1e6 - 19.2) < 0.05 # slow leg with bridge
assert abs(16/125e-9/1e6 - 128.0) < 0.5
print("PASS: efficiencies 50/80/88.9/94.1%; sustained 361.4, 180.7, 722.8, 76.8, 19.2, 128.0")Sustained rates quoted with their burst length; a bare 361.4 without L = 16 is unverifiable.
Bridge penalties shown separately from bus rates so a faster bridge can reclaim them.
The shared-bank row present wherever software places buffers: 180.7 each is a placement rule, not trivia.
Recompute the table whenever the arbitration changes: the NanoSoC specification's one bubble per burst stands for arbitration and address-to-data turnaround (a single master issuing back-to-back bursts still pays it), and a different arbiter or a register slice changes L + 1. The efficiency to recheck first is always the shortest burst the workload actually uses, not the 16-beat headline.
Watch what short bursts do to the table: at length 4 the efficiency is 80% and the fabric offers 307.2, not 361.4, and at length 1 it halves to 192.0. A master that issues single-beat register-style traffic across the fabric — common in bring-up code that pokes memory word by word — sees half the headline rate while the table still claims 361.4. Quote the sustained rate at the burst length the workload really uses, and distrust any budget whose L column is blank.
Because the DMA moves one word per clock when fed, so the stream rate is the source rate: the fabric's sustained 361.4. The stream adds no penalty of its own; starve the source and the stream follows it down.