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. Generating Decode from a Map Table
On this page

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

    Generating Decode from a Map Table

    One map table generating the SystemVerilog decode and the software header, with a script that proves they agree.

    Why it matters

    The hand-written decoder and the hand-written header agree on the day they are written and drift from the first engineering change order. Somebody widens a window in the RTL and forgets the header, or fixes a base in the header and forgets the RTL, and the classic failure writes itself: the register specification is out of sync with the RTL, drivers hit the wrong block, and the bug survives every review because each file looks right on its own. The cure is a single map table as data, with the decode, the headers and the documentation table all generated from it.

    The core idea

    The map table is a list of rows, each naming the region, its base and the bit slice the select compares. Three small emitters read the same rows in the full flow: one writes the SystemVerilog assigns, one writes the C defines, one writes the documentation table. The miniature below shows two of the three. Nothing else is allowed to state a base address. The generation step then asserts what generation guarantees: every region appears in every output, every emitted compare matches the row it came from, and every base satisfies its own equation. A mismatch fails the build instead of becoming a silicon bug.

    The script below is the whole flow in miniature for the four interconnect windows. It generates the decode text and the header text, then checks three things: each region has one select and one define from the same base, each compare names the slice from its own row, and each base really satisfies its equation. Add the second DMA and the script grows by one row, not by three coordinated edits.

    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
    REGIONS = [
        ("ROM", 0x0000_0000, 31, 14, 0x00000),
        ("SRAM0", 0x2000_0000, 31, 16, 0x2000),
        ("SRAM1", 0x2001_0000, 31, 16, 0x2001),
        ("APB", 0x4000_0000, 31, 16, 0x4000),
    ]
    
    def gen_sv(regions):
        lines = ["// generated from the map table; do not hand-edit"]
        for name, base, hi, lo, val in regions:
            lines.append(f"assign sel_{name.lower()} = (addr[{hi}:{lo}] == {(hi - lo + 1)}'h{val:X});")
        lines.append("assign sel_default = ~(sel_rom | sel_sram0 | sel_sram1 | sel_apb);")
        return "\n".join(lines) + "\n"
    
    def gen_header(regions):
        lines = ["/* generated from the map table; do not hand-edit */"]
        for name, base, hi, lo, val in regions:
            lines.append(f"#define {name}_BASE 0x{base:08X}")
        return "\n".join(lines) + "\n"
    
    sv_text = gen_sv(REGIONS)
    h_text = gen_header(REGIONS)
    for name, base, hi, lo, val in REGIONS:
        assert f"sel_{name.lower()}" in sv_text
        assert f"#define {name}_BASE 0x{base:08X}" in h_text
        assert f"addr[{hi}:{lo}]" in sv_text
        assert ((base >> lo) & ((1 << (hi - lo + 1)) - 1)) == val
    assert "sel_default" in sv_text
    print("PASS: 4 selects and 4 defines generated from one table and agree")

    Why hand-edited decode drifts

    A hand edit fixes the symptom in one file and leaves the other two stale, because nothing connects them except discipline, and discipline does not survive tapeout pressure. The generated flow inverts the burden: the table is the only file anyone edits, and the check that all outputs derive from it runs on every build. The review then reads the table, which fits on a slide, instead of diffing three files line by line. It is a habit worth carrying to every later project: state each address fact once, generate every copy, and let the build prove the copies agree.

    What the review looks for

    • The generated files carry a do-not-hand-edit banner and a map version stamp; any hand edit is a review finding.

    • The agreement check runs in the build, not on request, and its output is archived with the release.

    • The documentation table is generated too, so the document the software team reads cannot disagree with the RTL.

    Verify and debug

    Change one base in the table and rebuild: all three outputs must change together and the agreement check must still pass. If the check passes but simulation disagrees, the RTL under test was not rebuilt from the generated file, which means the build, not the map, is broken. Keep one golden copy of the generated outputs per map version so any drift shows up as a diff.

    Register spec out of sync with the RTL: a header generated from yesterday's map with a field offset or reset value that moved. The register-model-versus-RTL check catches it, but generation from one source stops it from happening at all.

    If asked how you keep decode, headers and docs consistent, answer with the pipeline: one table, three emitters, one agreement check in the build. Then name what breaks first without it: the header, because software changes fastest.

    Check yourself A reviewer finds sel_sram1 comparing against 0x2002 in the RTL but SRAM1_BASE as 0x20010000 in the header. What broke?

    Somebody hand-edited the RTL without regenerating, or regenerated the RTL without the header. In the generated flow this state cannot reach review: the table holds one value and the agreement check rebuilds both outputs from it.

    Related topics

    • Hierarchical Decode and Slot Decode

    • Next: Decode Timing and Pipelining

    • RTL: Generated Register Workflow

    Previous

    45 / 289

    Next: Decode Timing and Pipelining

    Up next

    Up nextDecode Timing and Pipelining

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Decode from Address Bits