AMBA AHB · Module 11
Sparse Memory Maps
Decoding sparse, non-power-of-two address regions on AHB — the choice between full decode (compare all upper bits, exact, robust) and partial decode (fewer bits, cheap, but the slave aliases across many addresses), plus range comparison for non-power-of-two sizes; why full decode and power-of-two regions are the robust default.
Chapter 11.4 assumed clean, power-of-two regions. Real systems aren't always so tidy: a sparse memory map has a few small regions scattered across a large, mostly-unmapped address space (a 256-byte peripheral here, a 1 KB block far away, vast gaps between). Decoding a sparse map forces two decisions this chapter examines. First, full decode vs partial decode: do you compare all the relevant upper bits (so a slave responds only at its exact region — robust, but a larger comparator), or only some bits (cheaper, but the slave aliases — appears at many addresses, so stray accesses hit it instead of the default slave, hiding bugs)? Second, non-power-of-two sizes: a region whose size isn't a power of two needs a range comparison (base ≤ HADDR < base+size) instead of a single upper-bits equality — costlier. The chapter's thesis: prefer full decode for robustness, and use power-of-two aligned regions to keep it cheap — accept aliasing only when area is genuinely critical.
1. What Is It?
A sparse memory map is an address map where a few small regions are scattered widely across a large address space, leaving most of it unmapped. Decoding it raises two issues:
- Full vs partial decode. Full decode compares all the upper address bits, so a slave responds only at its exact region. Partial decode compares only some bits (ignoring upper bits), which is cheaper but makes the slave alias — respond at many addresses.
- Non-power-of-two sizes. A power-of-two, aligned region decodes with a single upper-bits equality. A non-power-of-two (or unaligned) region needs a range comparison (
base ≤ HADDR < base+size) — more logic.
So a sparse memory map is the realistic case where regions are small and widely separated — common when peripherals (which need only a few registers, so tiny regions) are placed at well-separated base addresses across a large space. The "sparseness" (small regions, big gaps) is what makes the decode decisions matter: with tiny regions, the temptation to partial-decode (save comparator bits) is strong, but it causes aliasing; and small register blocks are often not naturally power-of-two-sized, raising the range-decode question. So a sparse memory map is the setting where full-vs-partial decode and power-of-two-vs-arbitrary sizing become real design choices.
2. Why Does It Exist?
The sparse-map decode problem exists because peripherals naturally want small register blocks at well-separated addresses, which creates tiny regions in a vast space — and the cost of comparing all the address bits for each tiny region tempts designers toward partial decode, which trades robustness for area.
The small-blocks-in-a-big-space reality is the root: peripherals typically need only a handful of registers — a UART might have 8–16 registers (tens of bytes), a GPIO a few. So their natural region size is tiny (hundreds of bytes). But they're placed at well-separated base addresses (for clean software addressing, expansion room, and hierarchical grouping) across a large (32-bit / 64-bit) space. So you get small regions far apart — a sparse map. This is normal — it's how peripheral address spaces look. So the sparse map exists because peripherals are small but spread out. So the decode must handle tiny regions in a big space.
The reason partial decode is tempting is comparator cost: to decode a tiny region exactly (full decode), you must compare many upper address bits (e.g. to pin a 256-byte region in a 4 GB space exactly, you compare the top 24 bits). That's a wide comparator. Partial decode compares fewer bits (say, just enough to distinguish this slave from its near neighbors), ignoring the upper bits — a smaller, faster comparator. With many peripherals, the area saving adds up. So partial decode exists as a cost optimization — fewer comparator bits per region. So the temptation exists because full decode of tiny scattered regions is comparator-expensive, and partial decode is cheaper.
The reason partial decode is risky (causing aliasing) is the cost it hides: by ignoring upper bits, a partial decoder makes the slave respond to any address whose compared bits match — i.e. the slave aliases, appearing at many addresses across the space (every address with the right low bits, regardless of the ignored upper bits). So the "unmapped gap" around the slave isn't really unmapped — it's full of aliases of the slave. Consequences: a stray access (a bug) to what should be an unmapped address might instead hit the aliased slave (corrupting it or reading it) rather than faulting at the default slave — so the bug is hidden (no fault) and may cause unexpected side effects (touching a real peripheral). Also, software might accidentally rely on an alias, creating fragile code. So partial decode trades robustness (exact decode, strays fault cleanly) for area (fewer comparator bits). So the sparse-map decode problem exists as this tradeoff: small scattered regions make full decode comparator-expensive, partial decode cheaper but aliasing-prone — and the design must choose. The reason full decode is the robust default is that aliasing's costs (hidden bugs, accidental side effects, fragile software) usually outweigh the comparator savings — and the savings can be recovered by using power-of-two aligned regions (which make even full decode a cheap upper-bits equality). So full decode + power-of-two regions resolves the tension. So the problem exists, and its robust resolution is full decode with power-of-two regions.
3. Mental Model
Model the full-vs-partial decode choice as checking ID at a members-only club — full decode is verifying the entire membership number (so only the one real member gets in, and any forged or wrong number is turned away to the front desk); partial decode is checking only the last two digits (faster, but now anyone whose number happens to end in those digits gets in — the member effectively has dozens of "doubles" who can walk past, and a gate-crasher with the right last two digits sails through instead of being stopped).
A members-only club checks IDs at the door. Full decode is verifying the entire membership number against the one real member — so only that member is admitted, and anyone with a wrong or forged number is turned away (sent to the front desk = the default slave). It takes longer (checking all the digits = a wider comparator) but it's exact: one number, one member, everyone else rejected. Partial decode is the lazy bouncer checking only the last two digits — fast, but now the member effectively has dozens of doubles: anyone whose number ends in those two digits is waved in as if they were the member (the slave aliases — responds at many addresses). Worse, a gate-crasher (a stray/buggy access) whose number happens to end in those digits sails right through — instead of being stopped at the front desk (faulting at the default slave), they get in and cause trouble (hit the real peripheral). So the lazy check is faster but lets impostors in and hides intrusions. The exact check is a bit slower but admits only the right member and turns everyone else away cleanly.
This captures the decode choice: the membership number = the address; checking the entire number = full decode (compare all upper bits); checking only two digits = partial decode (compare few bits); the dozens of doubles = aliasing (the slave at many addresses); the gate-crasher sailing through = a stray access hitting the aliased slave instead of faulting; the front desk = the default slave. Check the whole number (full decode) so only the real member gets in and impostors are turned away — don't let a lazy partial check create doubles and let gate-crashers through.
Watch aliasing under partial decode vs full decode:
Partial decode aliases; full decode is exact
4 cyclesThe model's lesson: check the whole membership number (full decode) — admit only the real member, turn impostors away. In the waveform, partial decode lets 0x4001_0000 alias to the slave, while full decode correctly routes it to the default slave.
4. Real Hardware Perspective
In hardware, the decode-bit choice is concrete: full decode is a wide equality (or range) comparator on all relevant upper bits; partial decode drops the upper bits; non-power-of-two regions need two magnitude comparators for the range check.
The full vs partial comparator is the core hardware difference. Full decode of a region of size 2^n aligned to 2^n: HSEL = (HADDR[31:n] == base[31:n]) — an equality on (32-n) bits. For a tiny region (small n), that's a wide comparison (many bits). Partial decode compares only a subset — say HADDR[m:n] == base[m:n] for some m < 31, ignoring HADDR[31:m+1]. Fewer bits → smaller, faster comparator → but the ignored upper bits mean the slave matches for any value of those bits → aliasing (the slave repeats every 2^(m+1) addresses). So in hardware, partial decode literally drops the upper comparator bits, trading width for aliasing. So the choice is a comparator-width decision with a robustness consequence.
The non-power-of-two range compare is the second hardware concern: a region of arbitrary size S at base B (where S isn't a power of two, or B isn't aligned to S) can't be matched by an upper-bits equality. It needs a range check: (HADDR >= B) && (HADDR < B+S) — two magnitude comparators (greater-equal and less-than) plus an AND. Magnitude comparators are bigger and slower than equality comparators, and there's one per region. So non-power-of-two regions cost more decode logic and add delay. This is why designers round region sizes up to the next power of two and align them: a 40-byte register block becomes a 64-byte (or larger) power-of-two region, decodable by a cheap equality — the wasted address space (24 bytes, or more) is free, but the decode is much cheaper. So in hardware, non-power-of-two regions need range comparators; rounding to power-of-two avoids them. So the hardware reality drives the recommendation: full decode (don't drop upper bits → no aliasing) on power-of-two aligned regions (equality, not range → cheap) — exact and cheap. Partial decode and range compares are the fallbacks when area is critical or arbitrary sizing is forced.
The practical middle ground: some designs partial-decode within a known-bounded block but full-decode the block's base. E.g. a peripheral block is fully decoded at the top level (its base is exact, so the block as a whole doesn't alias into other regions), but within the block, peripherals are partially decoded (a peripheral may alias within its own block's bounds, which is harmless if the whole block is reserved for peripherals). So aliasing can be contained within a fully-decoded block — acceptable because the aliases stay within reserved space, not bleeding into other regions or gaps. So in hardware, a common robust-and-cheap pattern is full decode at the block level, partial decode within the block — bounding any aliasing to reserved space. So the hardware perspective: choose decode width per the robustness/area tradeoff, prefer power-of-two regions to avoid range compares, and contain any partial decode within fully-decoded blocks.
5. System Architecture Perspective
At the system level, the sparse-map decode choice is a robustness-vs-area architectural decision, and the strong recommendation (full decode, power-of-two regions) reflects that robustness usually wins — with implications for software, debugging, and future expansion.
The robustness-vs-area framing is the core system tradeoff: full decode maximizes robustness (exact regions, strays fault cleanly at the default slave, no hidden bugs) at some area cost (wider comparators). Partial decode minimizes area (narrower comparators) at a robustness cost (aliasing — strays hit slaves, bugs hidden, fragile software). In most systems, the comparator-area difference is tiny relative to the total design (a few extra bits per region in a multi-million-gate SoC is negligible), while the robustness benefit is significant (clean fault behavior, debuggability). So the tradeoff usually favors full decode. So at the system level, full decode is the default because robustness vastly outweighs the small area cost — partial decode is reserved for extreme area-constrained designs (tiny ASICs, where every gate counts). So the architectural recommendation is robustness-first.
The debuggability and software-robustness view reinforces this: aliasing hides bugs — a stray access to a should-be-unmapped address silently hits an aliased slave (causing side effects, reading garbage) instead of faulting. With full decode, that stray access faults at the default slave (chapter 11.5) — a clean, attributable bus fault. So full decode preserves the illegal-address-behavior guarantees (chapter 11.5): strays fault, bugs surface. Aliasing undermines those guarantees (strays don't fault — they hit aliases). So full decode is what makes the default-slave / illegal-address machinery actually work for the sparse regions. Also, aliasing invites fragile software (code accidentally relying on an alias address that a later, fuller decode would break). So full decode protects both debugging and software robustness. So at the system level, full decode upholds the system's fault-handling and software-robustness properties.
The expansion and consistency view is the third point: a fully-decoded, power-of-two map leaves the unmapped gaps genuinely free for future expansion — you can add a peripheral in a gap, knowing nothing currently aliases there. An aliased (partial-decode) map pollutes the gaps with aliases, so adding a peripheral there might collide with an existing slave's alias — a nasty integration surprise. So full decode keeps the address space clean and expandable. And full, power-of-two decode keeps the map consistent with the simple "compare the upper bits" model that software and tools assume (chapter 11.4) — aliasing breaks that model (a slave at unexpected addresses). So at the system level, the sparse-map decode choice is a robustness/area tradeoff that strongly favors full decode on power-of-two aligned regions: it's exact (no aliasing), cheap (equality, not range), upholds the fault-handling guarantees, keeps software robust, and leaves the space clean for expansion. Partial decode and range compares are niche fallbacks for extreme area constraints or forced arbitrary sizing — to be used deliberately, with the aliasing contained (e.g. within a fully-decoded block) and documented. So a well-architected sparse map looks like the clean, power-of-two, fully-decoded map of chapter 11.4 — even when the regions are tiny and scattered.
6. Engineering Tradeoffs
Sparse-map decoding embodies the robustness-vs-area decode design.
- Full decode vs partial decode. Full decode is exact (slave only at its region, strays fault) at the cost of wider comparators. Partial decode is cheap (fewer bits) but aliases (slave at many addresses, strays hit it, bugs hidden). Full decode is the robust default; partial only for extreme area constraints.
- Power-of-two regions vs arbitrary sizes. Power-of-two aligned regions decode with a cheap equality and pack into a clean map at the cost of some over-allocated address space (free). Arbitrary sizes pack tightly but need range comparators (bigger, slower). Round up to power-of-two.
- Contained aliasing vs unbounded aliasing. If partial decode is necessary, contain the aliasing within a fully-decoded block (aliases stay in reserved space) — safer than unbounded aliasing that bleeds into gaps/other regions. Bound any aliasing.
- Clean gaps vs polluted gaps. Full decode leaves gaps genuinely unmapped (expandable, strays fault); aliasing pollutes gaps with slave aliases (collision risk on expansion, strays don't fault). Keep gaps clean.
The throughline: a sparse map (small regions, big gaps) forces full-vs-partial decode and power-of-two-vs-arbitrary sizing choices. Full decode (compare all upper bits) makes each slave respond only at its exact region — strays fault at the default slave, no aliasing — at a small comparator cost. Partial decode (fewer bits) is cheaper but aliases (slave at many addresses, strays hit it, bugs hidden). Non-power-of-two regions need range comparators. The robust default is full decode on power-of-two aligned regions — exact and cheap, upholding the fault-handling guarantees and keeping the map clean and expandable. Partial decode is a niche, area-driven fallback, with any aliasing contained.
7. Industry Example
Trace decode choices in a sparse peripheral map.
A system has tiny peripheral register blocks scattered in a 4 GB space.
- Full decode (the robust choice). Each peripheral's base is fully decoded: the UART at
0x4000_0000is selected only whenHADDR[31:8]matches (a 256-byte region, comparing the top 24 bits). So the UART responds only at0x4000_0000–0x4000_00FF. An access to0x4001_0000(a gap) matches no region → default slave → bus fault. Strays fault cleanly; no aliasing. - Power-of-two rounding. The UART actually has 40 bytes of registers, but its region is rounded up to 256 bytes (power-of-two) — so the decode is a cheap upper-bits equality, and the extra 216 bytes of address space (not memory) are simply unused within the region (or alias harmlessly within the UART's own reserved 256 bytes). Cheap decode, clean map.
- Contained partial decode (an area-driven case). Suppose an extremely area-constrained design partial-decodes peripherals within a fully-decoded 64 KB peripheral block at
0x4000_0000. The block is fully decoded (so it doesn't alias into other regions or gaps), but a peripheral within it might alias across the block's 64 KB. This is acceptable because the whole 64 KB is reserved for peripherals — the aliases stay in reserved space, never reaching real gaps or other slaves. The aliasing is contained and documented. - The aliasing-bug counter-example. Suppose instead a peripheral were partial-decoded unbounded — comparing only its low 8 bits, ignoring all upper bits. It would alias across the entire 4 GB. A wild-pointer read to
0x9000_0042(which should be an unmapped gap → fault) would instead alias to the peripheral's register at offset0x42— reading it (a side effect) and not faulting. The wild-pointer bug is now hidden, and the peripheral may have been disturbed. This is exactly the hazard full decode avoids.
The example shows the spectrum: full decode + power-of-two rounding (robust and cheap, the default), contained partial decode (acceptable when area-critical, aliasing bounded to reserved space), and unbounded partial decode (the hazard — aliasing across the whole space, hiding bugs). The robust, recommended design is full decode on power-of-two aligned regions.
8. Common Mistakes
9. Interview Insight
Sparse-map decoding is a robustness/area interview topic — the full-vs-partial tradeoff, aliasing, and the power-of-two recommendation are the signals.
The answer that lands gives the tradeoff and the recommendation: "A sparse map has small regions scattered in a large mostly-unmapped space, which forces two decode decisions. First, full decode versus partial decode. Full decode compares all the relevant upper address bits, so a slave responds only at its exact region — robust, because stray accesses to the gaps fault cleanly at the default slave — at the cost of a wider comparator. Partial decode compares only some bits, ignoring the upper ones — cheaper, but the slave aliases: it responds at many addresses, so a stray access to what should be an unmapped gap silently hits the slave instead of faulting, hiding the bug and possibly disturbing the peripheral. Second, region sizing: a power-of-two aligned region decodes with a single upper-bits equality, while a non-power-of-two or unaligned region needs a range comparison — two magnitude comparators, bigger and slower. So the robust recommendation is full decode on power-of-two aligned regions: it's exact (no aliasing, strays fault) and cheap (equality, not range), and it keeps the gaps clean for expansion. Partial decode is a niche, area-driven fallback — and if you must use it, contain the aliasing within a fully-decoded reserved block so it can't bleed into gaps or other regions." The full-vs-partial tradeoff, the aliasing hazard, and the power-of-two recommendation are the senior signals.
10. Practice Challenge
Reason from sparse-map decoding.
- Define it. Describe a sparse memory map and the two decode decisions it forces.
- Aliasing. Explain what aliasing is, how partial decode causes it, and why it's a problem.
- Read the waveform. From Figure 2, contrast what happens at
0x4001_0000under partial vs full decode. - Power-of-two. Explain why non-power-of-two regions cost more to decode and the standard fix.
- Contained partial decode. Explain when partial decode is acceptable and how to make it safe.
11. Key Takeaways
- A sparse memory map has small regions scattered across a large, mostly-unmapped space — forcing full-vs-partial decode and power-of-two-vs-arbitrary sizing choices.
- Full decode compares all relevant upper bits, so a slave responds only at its exact region — strays fault cleanly at the default slave (no aliasing) — at the cost of a wider comparator. This is the robust default.
- Partial decode compares fewer bits (cheaper) but causes aliasing — the slave responds at many addresses, so strays hit it instead of faulting, hiding bugs and disturbing peripherals.
- Non-power-of-two regions need a range comparison (two magnitude comparators) instead of a cheap upper-bits equality — so round sizes up to power-of-two and align them (free address space, cheap decode).
- Aliasing breaks the illegal-address fault guarantee for the aliased space — full decode preserves it (strays fault).
- The robust recommendation: full decode on power-of-two aligned regions — exact and cheap, fault guarantees upheld, gaps clean for expansion. Partial decode is a niche area-driven fallback, with any aliasing contained in a fully-decoded reserved block and documented.
12. What Comes Next
You now understand sparse-map decoding — full vs partial decode, aliasing, and the power-of-two recommendation. The final chapter of Module 11 turns to peripherals specifically:
- Peripheral Selection (next) — how peripherals are selected and addressed on the bus, including sub-decoding within a peripheral block.
To revisit the address map and illegal-address behavior this builds on, see The Address Map and Illegal Address Behavior.