Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutRoadmapCareersInternshipsFAQContactPrivacyTerms
Skip to lesson

Course contents

  • How to Use This Course
  • The NanoSoC Tour
  • Reading Path
  • SoC Glossary10
  • SoC Metrics Reference
  • SoC Cheatsheet
  • What SoC Integration Is12
  • Architecture and Budgets21
  • Memory Maps and Address Decode26
    • Designing the Address Map
      • What an Address Map Is
      • Sizes, Alignment and Power-of-Two Windows
      • Holes, Aliases and Reserved Space
      • Planning for Growth and Banks
    • Implementing Address Decode
    • Decode Errors and Default Slaves
    • Register Maps and Banks
    • Security and Privilege Regions
    • The Software View and the Hardware-Software Contract
    • IP-XACT and Automated Assembly
    • Memory-Map Verification
  • Interconnect and Bus Fabrics26
  • Clock and Reset Architecture24
  • Power Architecture21
  • Interrupts, DMA and Peripherals15
  • Memories, Boot and Security21
  • IP Integration and Handoff21
  • Top-Level Verification and Bring-up21
  • Package, Pads and I/O18
  • Chip Signoff and Tapeout18
  • SoC Interview Prep16
  • Capstone: Integrate NanoSoC14
Loading lesson

←Designing the Address Map
  1. Topics
  2. SoC Design
  3. Memory Maps and Address Decode
  4. Designing the Address Map
  5. Sizes, Alignment and Power-of-Two Windows
On this page

    Part 3 of 14, lesson 2 of 26Part 3 of 14/Memory Maps and Address Decode/Lesson 2 of 26All levels

    Sizes, Alignment and Power-of-Two Windows

    Why every window is a power of two on a multiple of its size, what that costs in decode, and a checker that proves it.

    Why it matters

    An integrator once sizes a window at an honest number, say 48 KB for a memory that might grow, and the decode that results needs two range comparisons instead of one equality check. Multiply that by every slave and the address path, which must settle inside one 96 MHz cycle on NanoSoC, starts to miss timing. Power-of-two sizing is not tidiness; it is what keeps the decode to a single comparison of the upper address bits. The NanoSoC map obeys the rule everywhere, and this lesson shows how to check that mechanically.

    The core idea

    A window of size 2 to the n whose base is a multiple of its size is selected by comparing only the top 32 minus n address bits. SRAM0 is 64 KB, which is 2 to the 16, sitting at 0x2000_0000, a multiple of 0x1_0000: the select is simply addr bits 31 down to 16 equal to 0x2000, and the low 16 bits are left over as the offset inside the memory. The ROM is 16 KB at zero, so its select is addr bits 31 down to 14 equal to zero. Each APB slot is 4 KB, 2 to the 12, so the slot number is addr bits 15 down to 12 and the offset inside the slot is bits 11 down to 0. One lesson to carry elsewhere: a 768 MiB window, three times 256 MiB, is not a power of two and can never be one equality compare, however convenient the number looks in a spreadsheet.

    Rounding up wastes address space, and the waste is easy to price. A peripheral needing 3 KB of registers takes a 4 KB slot and strands 1 KB; NanoSoC strands that inside every slot on purpose. A memory needing 40 KB takes 64 KB and strands 24 KB. The waste buys single-compare decode and room to grow the register block without moving the base, which is cheap next to a respin caused by an overlapping window.

    Diagram
    # not-run: NanoSoC window arithmetic, NanoSoC teaching values
    SRAM0: size 64 KB = 2^16, base 0x2000_0000, 0x2000_0000 mod 0x1_0000 = 0  -> select is addr[31:16] == 0x2000
    ROM:   size 16 KB = 2^14, base 0x0000_0000                              -> select is addr[31:14] == 0
    APB:   window 64 KB at 0x4000_0000, slot 4 KB = 2^12                    -> slot is addr[15:12], offset is addr[11:0]
    SRAM0 end: 0x2000_0000 + 0x1_0000 - 1 = 0x2000_FFFF; next base 0x2001_0000 (SRAM1, no gap, no overlap)

    The checker

    Two rules decide whether a map table is even plausible: each size is a power of two with a base aligned to it, and no two regions overlap. The script below checks both for the ten real NanoSoC regions, then proves it is awake by catching an injected duplicate of the DMA window and two regions buried inside a larger one. Comparing each region with the furthest-reaching earlier region, not just its immediate neighbour, is what makes the overlap test sound. Run it whenever the map table changes; it finishes in milliseconds and has caught more integration bugs than any review meeting.

    example.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    MAP = [
        ("BOOTROM", 0x0000_0000, 0x4000),
        ("SRAM0", 0x2000_0000, 0x1_0000),
        ("SRAM1", 0x2001_0000, 0x1_0000),
        ("DMA", 0x4000_0000, 0x1000),
        ("SBR", 0x4000_1000, 0x1000),
        ("UART", 0x4000_2000, 0x1000),
        ("SPI", 0x4000_3000, 0x1000),
        ("GPIO", 0x4000_4000, 0x1000),
        ("INTC", 0x4000_5000, 0x1000),
        ("RSTPWR", 0x4000_6000, 0x1000),
    ]
    
    def is_pow2(n):
        return n > 0 and (n & (n - 1)) == 0
    
    def check(regions):
        problems = []
        for name, base, size in regions:
            if not is_pow2(size):
                problems.append(f"{name}: size {size:#x} not a power of two")
            if base % size != 0:
                problems.append(f"{name}: base {base:#x} misaligned to {size:#x}")
        ordered = sorted(regions, key=lambda r: r[1])
        run_end, run_name = -1, ""
        for name, base, size in ordered:
            if base < run_end:  # overlaps the furthest-reaching earlier region
                problems.append(f"overlap: {run_name} contains {base:#x} ({name})")
            run_end = max(run_end, base + size)
            if base + size == run_end:
                run_name = name
        return problems
    
    assert check(MAP) == []
    bad = MAP + [("DMA_COPY", 0x4000_0000, 0x1000)]
    hits = check(bad)
    assert len(hits) == 1 and hits[0].startswith("overlap:")
    buried = [("BIG", 0x2000_0000, 0x4_0000), ("IN1", 0x2001_0000, 0x1000),
              ("IN2", 0x2002_0000, 0x1000)]  # two regions inside a large one
    assert len(check(buried)) == 2
    print("PASS: NanoSoC regions disjoint; duplicate and buried overlaps caught")

    What the review looks for

    • The checker output for the current map version is attached to the integration review, not a hand-drawn diagram.

    • Every base is shown with its alignment arithmetic, as in the worked block above, so a reviewer can verify one row in seconds.

    • Stranded space inside each window is labelled reserved, not left blank for a neighbour to claim later.

    Verify and debug

    If the checker reports an overlap, sort the table by base and walk the boundaries: the usual cause is a size typed in decimal kilobytes instead of hex, or an end address mistaken for a size. If it reports misalignment, the window was probably grown without moving the base. Both are map-table edits, never decoder edits; the decoder is generated from the table, which is the next topic's lesson.

    Address decode overlap or hole from windows that were not rounded to a power of two. Two slaves answer at once and the bus returns garbage, or a gap hangs the master. It is the first failure mode to guard against in any address map; the checker above is its standing guard.

    If asked why maps waste space on power-of-two rounding, answer in gates: one equality compare of the upper bits versus two magnitude compares and an AND per window, on a path that must close timing every cycle.

    Check yourself SRAM0 is 64 KB at 0x2000_0000. Which address bits select it, and which bits index inside it?

    The size is 2 to the 16, so the low 16 bits are the offset and the select is addr[31:16] equal to 0x2000. Bit 16 is the first bit that distinguishes SRAM0 from SRAM1.

    Related topics

    • What an Address Map Is

    • Next: Holes, Aliases and Reserved Space

    • Protocol First Principles: Address Maps and Decoding

    Previous

    37 / 289

    Next: Holes, Aliases and Reserved Space

    Up next

    Up nextHoles, Aliases and Reserved Space

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Planning for Growth and Banks