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
  • 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
    • Capstone Brief
    • Stage 1: Memory Map and Interconnect
      • Map Table, Overlap and Alignment Check
      • Decode, Response Codes and the Alias
      • Link Table, Bandwidth and the Bottleneck
    • Stage 2: Clocks, Resets, Power
    • Stage 3: Interrupts and Boot
    • Stage 4: Verification and Signoff
Loading lesson

←Stage 1: Memory Map and Interconnect
  1. Topics
  2. SoC Design
  3. Capstone: Integrate NanoSoC
  4. Stage 1: Memory Map and Interconnect
  5. Decode, Response Codes and the Alias
On this page

    Part 14 of 14, lesson 4 of 14Part 14 of 14/Capstone: Integrate NanoSoC/Lesson 4 of 14All levels

    Decode, Response Codes and the Alias

    Derive the crossbar and APB decode, check every hole's error code, and catch planted bug 4.

    Why it matters

    Only a read of DMA CTRL returns the right register. STATUS, LEN and KEY take PSLVERR, and a COUNT read silently returns STATUS with no error at all. The register map is correct and the slot select is correct, yet the block is unusable. The integrator fed the DMA's 8-bit paddr with word addresses apb_paddr[9:2], but the DMA decodes byte offsets, so offset 4 arrives as 1, which is unmapped, while offset 0x10 arrives as 0x04, which is STATUS. Address conventions differ per IP, and the checker for this lesson models the wiring from the bus address to the port.

    Goal, inputs and steps

    • Goal: the decode expressions, a response code for every hole, and the DMA port wired to the right address bits.

    • Inputs: the crossbar rule, the APB slot rule, the DMA's byte-offset map.

    • Steps: derive the selects; probe each hole in the checker; inject the word-address wiring and confirm only CTRL is reached correctly and COUNT lands on STATUS; restore.

    The core idea

    The crossbar compares addr[31:14] == 0 for ROM, addr[31:16] == 0x2000 and 0x2001 for the SRAMs, and addr[31:16] == 0x4000 for the APB bridge; anything else falls to the default slave. Inside the window the APB decoder uses addr[15:12] as the slot, 0 to 6 mapped, 7 to 15 reserved. Holes H1, H2 and H4 reach no slave, so the default slave answers DECERR (2'b11) within 2 clk_sys cycles; hole H3 reaches the APB decoder's default slave, which answers PSLVERR, turned into AXI SLVERR (2'b10). Secure-only SRAM1 and RSTPWR return SLVERR to non-secure accesses. The DMA takes apb_paddr[7:0] as byte offsets; offsets 0x14 to 0xFF inside each 256-byte image still give PSLVERR.

    Diagram
    # not-run: format fragment, not executable here
    # crossbar selects           apb slot select
    addr[31:14] == 0      -> ROM  addr[15:12] == 0 -> DMA ... == 6 -> RSTPWR
    addr[31:16] == 0x2000 -> SRAM0                == 7..15 -> default slave (PSLVERR)
    addr[31:16] == 0x2001 -> SRAM1  H1/H2/H4 -> fabric default slave (DECERR)

    The checker

    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
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    MAP = [
        ("BOOTROM", 0x00000000, 0x4000),
        ("SRAM0", 0x20000000, 0x10000),
        ("SRAM1", 0x20010000, 0x10000),
        ("DMA", 0x40000000, 0x1000),
        ("SBR", 0x40001000, 0x1000),
        ("UART", 0x40002000, 0x1000),
        ("SPI", 0x40003000, 0x1000),
        ("GPIO", 0x40004000, 0x1000),
        ("INTC", 0x40005000, 0x1000),
        ("RSTPWR", 0x40006000, 0x1000),
    ]
    SECURE_ONLY = {"SRAM1", "RSTPWR"}
    SLOTS = ["DMA", "SBR", "UART", "SPI", "GPIO", "INTC", "RSTPWR"]
    T_CPU, T_SYS, T_PER = 1e9 / 192e6, 1e9 / 96e6, 1e9 / 24e6
    def end_of(base, size):
        return base + size - 1
    def overlaps(entries=MAP):
        bad = []
        xs = sorted(entries, key=lambda e: e[1])
        for a, b in zip(xs, xs[1:]):
            if b[1] < a[1] + a[2]:
                bad.append((a[0], b[0]))
        return bad
    def misaligned(entries=MAP):
        return [n for n, b, s in entries if b % s != 0]
    def decode(addr, slots=SLOTS):
        if addr >> 14 == 0:
            return "BOOTROM"
        if addr >> 16 == 0x2000:
            return "SRAM0"
        if addr >> 16 == 0x2001:
            return "SRAM1"
        if addr >> 16 == 0x4000:
            slot = (addr >> 12) & 0xF
            return slots[slot] if slot < 7 else "H3"
        if addr < 0x20000000:
            return "H1"
        if addr < 0x40000000:
            return "H2"
        return "H4"
    def response(addr, secure, slots=SLOTS):
        t = decode(addr, slots)
        if t in ("H1", "H2", "H4"):
            return "DECERR"
        if t == "H3":
            return "SLVERR"
        if t in SECURE_ONLY and not secure:
            return "SLVERR"
        return "OK"
    def sustained(peak, beats):
        return peak * beats / (beats + 1)
    
    # ---- this lesson starts here ----
    
    assert decode(0x00000000) == "BOOTROM" and decode(0x2000FFFF) == "SRAM0"
    assert decode(0x20010000) == "SRAM1" and decode(0x40006000) == "RSTPWR"
    assert decode(0x00004000) == "H1" and decode(0x20020000) == "H2"
    assert decode(0x40007000) == "H3" and decode(0x40010000) == "H4"
    assert response(0x00004000, True) == "DECERR" and response(0x40007000, True) == "SLVERR"
    assert response(0x40010000, True) == "DECERR"
    assert response(0x20010000, False) == "SLVERR" and response(0x20010000, True) == "OK"
    REG = {0x00: "CTRL", 0x04: "STATUS", 0x08: "LEN", 0x0C: "KEY", 0x10: "COUNT"}
    def dma_read(bus_addr, lo, hi):
        # the bus address has 12 bits; the DMA port paddr has 8 and sees bits [hi:lo] of it
        if hi - lo != 7:
            return "WIDTH"            # a width mismatch is a lint error, never a silent truncation
        return REG.get(((bus_addr & 0xFFF) >> lo) & 0xFF, "PSLVERR")
    def reached(lo, hi):
        return [dma_read(0x40000000 + o, lo, hi) for o in sorted(REG)]
    assert reached(0, 7) == ["CTRL", "STATUS", "LEN", "KEY", "COUNT"]                    # apb_paddr[7:0]: correct
    assert reached(2, 9) == ["CTRL", "PSLVERR", "PSLVERR", "PSLVERR", "STATUS"]          # apb_paddr[9:2]: bug 4
    assert reached(4, 11) == ["CTRL", "CTRL", "CTRL", "CTRL", "PSLVERR"]                 # apb_paddr[11:4]: worse
    assert reached(0, 9)[0] == "WIDTH"
    assert dma_read(0x40000104, 0, 7) == "STATUS" and dma_read(0x40000114, 0, 7) == "PSLVERR"   # the alias, and offsets past 0x10
    print("PASS: decode and codes agree; word-address wiring reaches 1 register right, 3 take PSLVERR, COUNT returns STATUS")

    Expected output

    One PASS line. The three reached lists are the bug-4 demonstration: the correct connection gives all five registers; the word-address connection leaves CTRL right, STATUS, LEN and KEY on PSLVERR, and COUNT silently reading STATUS because 0x10 shifted right by 2 is 0x04. The assert on a 10-bit slice shows the width lint refusing a mismatch.

    What each failure means

    A DECERR/SLVERR swap means the hole addressing was guessed: H3 is inside the APB window and answers SLVERR, the rest DECERR. A secure-column failure means AxPROT[1] was read backwards: 0 is secure, 1 is non-secure. A wrong list from the correct connection means the slice bounds were misread: the port wants bits [7:0] of the bus address, not a word address.

    Hint ladder

    Hint 1: list which offsets the RTL case statement actually matches: 0x00, 04, 08, 0C, 10. Hint 2: shift each right by two and look the result up in the same table. Hint 3: 0 lands on CTRL correctly, and 0x10 lands on 4, which is STATUS, not COUNT, so a COUNT read returns the wrong register with no error. Answer: connect apb_paddr[7:0]; treat port-width mismatches as lint errors and keep a per-IP address-convention table in the integration spec.

    The worse variant of the same bug: passing apb_paddr[11:4] when the bus is 12 bits wide and the port is 8. Every access aliases across images and the checker above still strands registers, but at stranger addresses.

    If asked why CTRL looked fine, derive it live: byte offset 0x00 shifted right by 2 is still 0, the fixed point of the shift, so CTRL is the survivor of any shift-by-two wiring error, while 0x10 lands on 0x04 and a COUNT read returns STATUS, the wrong register, with no error.

    Check yourself Software must never probe holes, yet the default slave exists. What is it for?

    A wild pointer or a bad linker script gets a clean DECERR instead of a hang, turning a freeze into a diagnosable fault.

    Related topics

    • Back: Map Table, Overlap and Alignment Check

    • Next: Link Table, Bandwidth and the Bottleneck

    • RTL Interview: Unmapped Decode and SLVERR

    Previous

    269 / 289

    Next: Link Table, Bandwidth and the Bottleneck

    Up next

    Up nextLink Table, Bandwidth and the Bottleneck

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error