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
    • Implementing Address Decode
      • Decode from Address Bits
      • Hierarchical Decode and Slot Decode
      • Generating Decode from a Map Table
      • Decode Timing and Pipelining
    • 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

←Implementing Address Decode
  1. Topics
  2. SoC Design
  3. Memory Maps and Address Decode
  4. Implementing Address Decode
  5. Decode from Address Bits
On this page

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

    Decode from Address Bits

    The four NanoSoC select equations, one-hot with a default, as a compiling module plus a truth table that proves it.

    Why it matters

    Every AXI transaction on NanoSoC passes through one combinational decision: which of the four slaves, or the default, owns this address. Get one bit wrong and a peripheral write lands in SRAM, or SRAM1 opens to non-secure software. The equations are short enough to hold in your head, which is exactly why they get hand-edited, and hand edits to decode are how two slaves end up answering at once. Write them once, check them with a table, and never touch them by hand again.

    The core idea

    Each window is one equality compare, taken straight from the map alignment of the previous topic. The ROM window is 16 KB at zero, so bits 31 down to 14 are all zero. SRAM0 and SRAM1 are 64 KB windows, so bits 31 down to 16 name the bank: 0x2000 or 0x2001. The APB bridge owns the 64 KB window at 0x4000_0000, so bits 31 down to 16 equal 0x4000. Anything else falls through to the default slave, which answers holes with DECERR. A secure-filter violation gets its own select and its own responder, never the default: the fault responder answers SLVERR with no data. The distinction is the lesson to keep, and it is semantic before it is about security: DECERR means no slave decoded the address, SLVERR means a slave (or its filter) refused it. SRAM1 exists and refused, so it answers SLVERR; answering DECERR would misreport an existing region as absent and send anyone debugging the access to the wrong level of the decode. As a defence-in-depth remark, not the reason: designs that care can additionally hide existence from a prober, but that is a separate policy choice, not what the response codes are for. Exactly one of the six outputs fires for every possible address.

    nano_addr_dec.sv
    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
    // NanoSoC teaching values: first-level interconnect decode plus the SRAM1 secure filter.
    // Holes select sel_default (answered DECERR); a secure violation selects sel_fault
    // (answered SLVERR by the fault responder, never the default slave).
    // The RSTPWR per-slot secure check lives in the APB slot decoder (next lesson).
    module nano_addr_dec (
      input  logic [31:0] addr,
      input  logic        prot_ns, // AxPROT[1]: 1 = non-secure
      output logic        sel_rom,
      output logic        sel_sram0,
      output logic        sel_sram1,
      output logic        sel_apb,
      output logic        sel_default,
      output logic        sel_fault
    );
      logic in_rom, in_sram0, in_sram1, in_apb;
      logic sram1_ns;
    
      assign in_rom   = (addr[31:14] == 18'h0);
      assign in_sram0 = (addr[31:16] == 16'h2000);
      assign in_sram1 = (addr[31:16] == 16'h2001);
      assign in_apb   = (addr[31:16] == 16'h4000);
    
      assign sram1_ns    = in_sram1 & prot_ns;
      assign sel_rom     = in_rom;
      assign sel_sram0   = in_sram0;
      assign sel_sram1   = in_sram1 & ~sram1_ns;
      assign sel_apb     = in_apb;
      assign sel_default = ~(in_rom | in_sram0 | in_sram1 | in_apb);
      assign sel_fault   = sram1_ns;
    endmodule

    The truth table

    The module above compiles cleanly, but compilation only proves the syntax. The table below proves the behaviour: it decodes the same addresses the same way, including the two cases newcomers get wrong, that hole H3 still selects APB at this level and that a non-secure SRAM1 access selects the fault responder, never the default and never the memory.

    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
    def decode(addr, prot_ns):
        in_rom = (addr >> 14) == 0
        in_sram0 = ((addr >> 16) & 0xFFFF) == 0x2000
        in_sram1 = ((addr >> 16) & 0xFFFF) == 0x2001
        in_apb = ((addr >> 16) & 0xFFFF) == 0x4000
        fault = bool(in_sram1 and prot_ns)
        if in_rom:
            sel = "ROM"
        elif in_sram0:
            sel = "SRAM0"
        elif in_sram1 and not fault:
            sel = "SRAM1"
        elif fault:
            sel = "FAULT"
        elif in_apb:
            sel = "APB"
        else:
            sel = "DEFAULT"
        return sel, fault
    
    cases = [
        (0x0000_0000, 0, ("ROM", False)),
        (0x0000_3FFF, 0, ("ROM", False)),
        (0x0000_4000, 0, ("DEFAULT", False)),
        (0x2000_0000, 0, ("SRAM0", False)),
        (0x2000_FFFF, 0, ("SRAM0", False)),
        (0x2001_0000, 0, ("SRAM1", False)),
        (0x2001_0000, 1, ("FAULT", True)),
        (0x4000_1000, 0, ("APB", False)),
        (0x4000_7000, 0, ("APB", False)),
        (0x4001_0000, 0, ("DEFAULT", False)),
        (0xFFFF_FFFF, 0, ("DEFAULT", False)),
    ]
    for addr, ns, want in cases:
        assert decode(addr, ns) == want, hex(addr)
    print("PASS: 11 decode cases incl. H3-as-APB and secure SRAM1 to FAULT")

    What the review looks for

    • The four equations match the map table bit for bit: window size to compare width, base to compare value.

    • Holes select only the default (DECERR) and a secure violation selects only the fault responder (SLVERR), so the two error codes can never be confused.

    • Secure filtering is visible beside the selects with its own output, not buried inside one equation.

    Verify and debug

    Exhaust the boundaries, not the middles: each window contributes its base, its last byte and one address either side, plus both security states for SRAM1. The eleven cases above cover the main boundaries; the full rule, about twenty addresses including 0x1FFF_FFFF, 0x2001_FFFF, 0x2002_0000 and 0x4000_FFFF, is generated from the map table so a new window adds its own boundary rows automatically.

    Address truncation in a bridge or adapter: a 40-bit address narrowed to 32 bits aliases everything above 4 GB onto low memory. The lint check is a port-width comparison, and the directed test is a high-address access that must reach the default slave, not a mirror of ROM.

    If asked to decode a window by hand, state the width from the size, the value from the base, and the default as the NOR of the rest. Then volunteer the secure-fault output before being asked.

    Check yourself Why does the fault get its own select instead of sharing the default slave?

    The default slave answers DECERR, which means the address does not exist. SRAM1 exists and refused, so it must answer SLVERR with no data; sharing the default would give faulted accesses the wrong code and send the debugger looking for a missing region that is really there. (Hiding existence from a prober is a separate policy choice.)

    Related topics

    • Next: Hierarchical Decode and Slot Decode

    • RTL: Address Decode

    • Protocol First Principles: Access Attributes

    Previous

    43 / 289

    Next: Hierarchical Decode and Slot Decode

    Up next

    Up nextHierarchical Decode and Slot Decode

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Generating Decode from a Map Table
    • Decode Timing and Pipelining