A model answer is a memorised spine: when the question is a small chip with a CPU, a DMA, SRAMs and slow peripherals, the spine already fits and the session becomes adaptation instead of invention. NanoSoC is that spine. This lesson walks it exactly as the timed plan dictates, with every number taken from the NanoSoC reference tables, so the reader can reproduce it cold.
Requirements as budgets: two masters (CPU, stream bridge), four fabric slaves, 361.4 MB/s sustained per link, 722.8 MB/s across both SRAM banks, three power states from 82.74 mW down to 1.40 mW, two security regions. Block diagram: the CPU through its asynchronous bridge and the stream bridge into a two-by-four fabric; ROM, two SRAM banks and an AXI-to-APB bridge as slaves; fast APB segment for DMA and bridge registers, slow 24 MHz segment behind a request-and-acknowledge bridge for UART, SPI, GPIO, controller and reset block. Every crossing is named: FIFOs for AXI, handshake for APB, two flops for the interrupt levels.
Address map: ROM at zero, SRAM0 and SRAM1 at 0x2000_0000 and 0x2001_0000, seven 4 KB peripheral slots from 0x4000_0000, fabric holes answering DECERR, the APB window's reserved slots answering SLVERR through the bridge. Fabric: round robin with one idle cycle on master change, burst efficiency 16 over 17, the 4 KB rule enforced by splitting in the master. Clocks: 24 MHz crystal, PLL times 32 to 768 MHz, divide by 4 to 192 and by 8 to 96, peripherals bypassing the PLL. Resets: four sources, three per-domain nets, release order slaves first and CPU last, 53.948 us from power-on to the CPU leaving reset. Power: always-on island with the controller, peripherals, SRAM0 and ROM; switchable domain for the rest; 114 isolation clamps; wake in 14.073 us. Interrupts: six sources, fixed priority, 135.4 ns best and 182.3 ns worst from DMA interrupt to CPU. Boot: the ROM code reads the reset cause first; a wake skips the SRAM0 clear and jumps to the retained image, a cold boot clears 4 KB, loads the image over SPI and takes 12.581 ms, dominated by the 10.928 ms SPI load at 86.9 percent.
# The model answer as checks: every spine number re-derived from its inputs.
t_sys, t_cpu, t_per = 1 / 96e6, 1 / 192e6, 1 / 24e6
# Map: decode windows (ROM 16 KB, SRAM0, SRAM1, APB window 64 KB), each base aligned to its size.
windows = [("ROM", 0x0000_0000, 16 * 1024), ("SRAM0", 0x2000_0000, 64 * 1024),
("SRAM1", 0x2001_0000, 64 * 1024), ("APB", 0x4000_0000, 64 * 1024)]
def aligned(base, size):
return size & (size - 1) == 0 and base % size == 0
for name, base, size in windows:
assert aligned(base, size), (name, hex(base), size)
assert not aligned(0x2000_8000, 64 * 1024), "a misaligned base must be caught"
assert 7 * 4096 <= 64 * 1024, "seven 4 KB peripheral slots fit the APB window"
spans = sorted((b, b + s) for _, b, s in windows)
assert all(spans[i][1] <= spans[i + 1][0] for i in range(len(spans) - 1)), "overlap"
# Fabric: sustained rate and two-bank total; same-bank sharing halves it.
sustained = 4 * 96e6 * 16 / 17 / 1e6
assert abs(sustained - 361.4) < 0.1, sustained
assert abs(2 * sustained - 722.8) < 0.2, 2 * sustained
assert abs(sustained / 2 - 180.7) < 0.1, sustained / 2
# Reset release chain: oscillator, per-domain releases, PLL, gates, fabric, then CPU.
t_fabric = 18 * t_sys
t_cpu_rel = 8 * t_sys + 2 * t_cpu
hw_us = ((1024 + 2 + 260 + 2) * t_per + t_fabric + t_cpu_rel) * 1e6
assert abs(hw_us - 53.948) < 0.001, hw_us
assert t_cpu_rel > 0 and t_fabric > 0 # fabric releases first, CPU last
# Wake: switch ramp, power good, PLL relock, gates, then the system-clock steps.
wake_ns = ((64 + 8 + 256 + 2) * t_per + (4 + 18 + 8) * t_sys + 2 * t_cpu) * 1e9
assert abs(wake_ns - 14072.9) < 0.1, wake_ns
# Boot: SPI load share of the cold-boot total (microseconds).
spi_us = (8192 + 4) * 8 / 6e6 * 1e6
boot_us = 12580.917
assert abs(spi_us - 10928.0) < 0.01 and abs(spi_us / boot_us - 0.869) < 0.001
print(f"map aligned; fabric {sustained:.1f} MB/s, banks {2 * sustained:.1f}, shared {sustained / 2:.1f}")
print(f"hardware reset {hw_us:.3f} us; wake {wake_ns:.1f} ns; SPI load {spi_us / boot_us * 100:.1f}% of {boot_us / 1000:.3f} ms boot")
print("PASS: spine numbers agree with the reference tables")The spine is complete: no box from the timed plan is missing, and each closes with its total.
Every number traces to the NanoSoC tables: periods, map constants, latency table, boot ledger, power table.
The two known traps are named unprompted: the same-bank sharing that halves stream rate, and the DMA alias from the 8-bit port behind a 12-bit slot.
Reproduce the spine from memory on one page: diagram, map rows, four totals. Then check each number against the NanoSoC Tour and the lessons that own it: map, fabric, clocks, resets and power, interrupts, boot, budgets. A number you cannot source is a number you must label illustrative before the interviewer asks.
It is the sum of both banks at burst efficiency; a single stream sees 361.4, two streams sharing one bank see 180.7 each, and the APB and peripheral paths are far slower. Quote the conditions with the number or the number misleads.