Every review board asks the same question about a number: where was it earned? NanoSoC quotes 82.74 mW, 0.655 mm2, 682.6 MB/s and 12.581 ms across this course, and each of them must trace back to a requirement, a derivation and a check. Power, area, stream bandwidth and boot time each get that treatment below, recomputed from the per-block values. When a later lesson quotes one of these numbers, this trace is the receipt, and it is the worked example the first two lessons were building toward.
Requirement | Budget derivation | Result and margin |
|---|---|---|
Run a 64-word DMA job on 90 mW or less (illustrative cap) | Sum of per-block dynamic plus leakage in RUN | 82.74 mW; margin 7.26 mW (8.1% of the cap); CPU core alone 39.20 mW, 47.4% of RUN |
Idle under 20 mW, sleep under 2 mW (illustrative caps) | Gate CPU, DMA and SBR clocks; switch PD_SW off | IDLE 16.89 mW; SLEEP 1.40 mW with SRAM0 retained |
Fit the core in about 70% utilisation | Block area plus 12% routing overhead against 0.940 | 0.585 plus 0.070 is 0.655; utilisation 69.7% |
Stream 64 words without stalling the fabric | 512 bytes in 72 system clocks against both SRAM banks | Demand 682.6 MB/s against 722.8 sustained; margin 5.6% |
Boot an 8 KB image inside 20 ms (illustrative cap) | Reset chain plus ROM steps plus SPI load | 12.581 ms; SPI load 86.9%, banner 11.0% |
Two derivations carry the weight. Power is an addition over eighteen rows (the 16 blocks, 15 modules plus the PLL macro, with the CPU core and its cache and the power switches listed separately) with three operating points each, and the script checks all three totals plus the CPU share. Bandwidth is a placement argument: the SBR reads from one SRAM bank while writing the other, so the job's 512 bytes move at the combined sustained rate of 722.8 MB/s, and the 72-clock job fits with 5.6% margin. Put the stream bridge's source and destination in the same bank and each stream halves to 180.7 MB/s, which is why the trace also constrains buffer placement, not just the fabric. The 5.6% is an average over the 72-clock job, which is the fill-and-drain model with one word per clock and no burst bubbles. Counting one bubble per 16-beat burst, the four read bursts take 68 clocks and the job about 76 clocks (792 ns); during its 64 beats the read channel then needs 384 MB/s against 361.4 MB/s sustained. Read the 5.6% as planning margin on the average, not as peak headroom.
The script below re-derives the power totals, the area roll-up and the stream-bandwidth margin from the per-block specification values, asserting every total the course quotes. It is long because addition over eighteen rows is long; that is the point. A budget earned by a script stays earned when the next engineer changes a block.
# Per block: (area, dyn_run, leak, dyn_idle, dyn_sleep, leak_sleep).
# NanoSoC specification section 10, power in mW, area in mm2.
BLOCKS = {
"cpu": (0.150, 38.00, 1.20, 1.90, 0.00, 0.02),
"icache": (0.025, 4.00, 0.30, 0.20, 0.00, 0.01),
"axi_cdc": (0.015, 3.00, 0.12, 0.30, 0.00, 0.00),
"xbar": (0.040, 5.00, 0.32, 0.50, 0.00, 0.01),
"dma": (0.012, 3.00, 0.10, 0.00, 0.00, 0.00),
"sbr": (0.010, 2.00, 0.08, 0.00, 0.00, 0.00),
"axi2apb": (0.006, 1.00, 0.05, 0.10, 0.00, 0.00),
"apb_cdc": (0.004, 0.50, 0.03, 0.05, 0.00, 0.00),
"sram1": (0.100, 6.00, 1.20, 0.30, 0.00, 0.02),
"switches": (0.010, 0.00, 0.08, 0.00, 0.00, 0.00),
"sram0": (0.100, 6.00, 1.20, 0.30, 0.00, 0.72),
"rom": (0.012, 1.00, 0.14, 0.00, 0.00, 0.14),
"uart": (0.008, 0.30, 0.06, 0.30, 0.03, 0.06),
"spi": (0.008, 0.40, 0.06, 0.40, 0.04, 0.06),
"gpio": (0.006, 0.20, 0.05, 0.20, 0.02, 0.05),
"intc": (0.004, 0.20, 0.03, 0.20, 0.02, 0.03),
"rstpwr": (0.015, 0.50, 0.12, 0.50, 0.05, 0.12),
"pll": (0.060, 6.00, 0.50, 6.00, 0.00, 0.00),
}
def col(i):
return sum(v[i] for v in BLOCKS.values())
area = col(0)
assert abs(area - 0.585) < 1e-9, area
core = area * 1.12 # 12% clock tree, routing and fill
assert abs(core - 0.655) < 0.001, core
assert abs(core / 0.940 - 0.697) < 0.001 # utilisation 69.7%
run = col(1) + col(2)
assert abs(run - 82.74) < 0.01, run
assert abs(col(1) - 77.10) < 1e-9 and abs(col(2) - 5.64) < 1e-9
cpu_share = (38.00 + 1.20) / run
assert abs(cpu_share - 0.474) < 0.001 # CPU core is 47.4% of RUN
assert abs(col(3) + col(2) - 16.89) < 0.01 # IDLE
assert abs(col(4) + col(5) - 1.40) < 0.01 # SLEEP, SRAM0 retained
job_bytes = 2 * 64 * 4 # 64-word stream: read bank plus write bank
job_ns = 72 * 10.417 # N + 8 system clocks
demand = job_bytes / job_ns * 1000 # bytes/ns -> MB/s
supply = 2 * 361.4 # both SRAM banks at burst-16 sustained
assert abs(demand - 682.6) < 0.5, demand
margin = (supply - demand) / supply
assert margin > 0.05, margin
print("PASS: 82.74/16.89/1.40 mW; area 69.7%%; stream margin %.1f%%"
% (margin * 100))The trace recomputes from per-block values, never by quoting a total from memory.
Every requirement in the left column carries its cap, condition and owner from the earlier lessons.
Any block change re-runs the script before the budget table is updated.
When a budget breaks, the script says which column moved: power, area or bandwidth. Resist adjusting the margin first; the margin is the last defence, and spending it silently is how a project discovers at signoff that every budget is exact and none is met. The boot-time half of the trace was already recomputed in the flow topic; together the two scripts cover all four budgets.
RUN rises to 84.74 mW, still inside the 90 mW cap, and the margin falls from 7.26 mW to 5.26 mW (5.8%); the area roll-up also gains the block plus overhead. Nothing breaks unless a cap binds; the trace re-runs, the margins shrink on the record, and the owner of the new block signs the row.