The first architecture argument on NanoSoC was whether to bother with hierarchy at all. Sixteen blocks on the list, one engineer, one synthesis run that finishes before lunch: flat is faster to start. The argument ended when the power plan landed. One power domain switches off while the other stays on, one clock segment runs at 24 MHz while the fabric runs at 96 MHz, and the APB peripherals sit behind a bridge with different timing. Flattening those seams would not remove them; it would only hide them inside one netlist where every tool must rediscover them. Hierarchy does not create the seams. It names them.
Hierarchy pays when the design has seams that tools or teams must respect independently: separate clocks needing separate trees, separate power domains needing separate intent, separate protocols needing separate verification, or simply more gates than one run can hold. It costs interface effort at every boundary: budgeted timing, documented protocols, synchronisers for crossings, and verification that the two sides agree. The integrator's test is a balance: partition where the seam already exists for electrical reasons, and resist partitioning a uniform synchronous block just to mirror the org chart.
Shape | Pays when | Costs | NanoSoC example |
|---|---|---|---|
Flat | One clock, one power domain, one owner, fits the tools | Any seam is implicit and re-derived by every tool | Would hide the PD_SW boundary inside one netlist |
Two-level hierarchy | Seams follow clock, power or protocol lines | Budgeted interfaces at each boundary | System side versus peripheral side behind the APB bridge |
Deep hierarchy | Many owners, reuse, very large gate count | Feedthroughs, budget fragmentation, integration risk | Not used on NanoSoC; would be overhead here |
NanoSoC's gate count never forces hierarchy, so the decision rests on seams. Count them from the NanoSoC specification's crossing inventory: three clock domains, two power domains, two bus protocols plus two stream channels, and ten clock-domain crossings in ten groups (X1 to X10). A flat netlist still contains all ten crossings; the hierarchical one assigns each to a named bridge or synchroniser with an owner. The check below rebuilds that inventory by mechanism and proves every crossing names its mechanism — drop a row or blank a mechanism and it fails.
# NanoSoC specification, crossings: X1..X10 with from-domain, to-domain, mechanism.
crossings = [
("X1", "clk_cpu", "clk_sys", "async-fifo"),
("X2", "clk_sys", "clk_cpu", "async-fifo"),
("X3", "clk_sys", "clk_per", "req-ack"),
("X4", "clk_per", "clk_sys", "req-ack"),
("X5", "clk_sys", "clk_per", "level-sync"),
("X6", "clk_per", "clk_cpu", "level-sync"),
("X7", "clk_per", "clk_sys", "reset-sync"),
("X8", "clk_per", "clk_cpu", "reset-sync"),
("X9", "pads", "clk_per", "other-single-bit"),
("X10", "clk_per", "clk_sys", "other-single-bit"),
]
from collections import Counter
counts = Counter(mech for _, _, _, mech in crossings)
assert counts == {"async-fifo": 2, "req-ack": 2, "level-sync": 2,
"reset-sync": 2, "other-single-bit": 2}, counts
assert len(crossings) == 10 and all(mech for *_, mech in crossings)
assert [c[0] for c in crossings if c[1] == "pads"] == ["X9"] # only X9 is external
multi = [c for c in crossings if c[3] in ("async-fifo", "req-ack")]
assert len(multi) == 4 # every multi-bit crossing has a FIFO or handshake
print(f"PASS: 10 crossings by mechanism {dict(counts)}; each names its bridge")Each partition is justified by a clock, power, protocol or ownership seam, named in one line.
No partition exists only to mirror team structure; uniform logic stays together.
The top level owns the seam inventory: every crossing names its bridge and its mechanism.
List every top-level port and tag it with the seam it serves: clock, power, protocol or test. Any port with no tag is a candidate feedthrough, which the next lesson removes. Compare the partition list against the clock-domain plan and the power-state table; a domain with blocks in two partitions, or a partition spanning two power domains without an isolation plan, is a boundary drawn in the wrong place.
Deep hierarchy has a failure mode worth naming: each extra level slices budgets thinner and breeds feedthroughs, so a five-deep tree of uniform logic pays integration tax for no electrical reason. The rule of thumb is two levels for a chip of this size — subsystem then block — with a third level only where a genuine seam (the power switch, the async bridge) demands its own budget. Count the levels on any proposal; beyond three, ask which seam each level serves.
Because its seams are electrical, not organisational: three clocks, two power domains and three interface styles. Hierarchy names the ten crossings so each gets a bridge, a budget and an owner.