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 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
      • What IP-XACT Describes
      • Generating the Top from One Description
      • Reviewing Generated Output
    • 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

←IP-XACT and Automated Assembly
  1. Topics
  2. SoC Design
  3. Memory Maps and Address Decode
  4. IP-XACT and Automated Assembly
  5. Generating the Top from One Description
On this page

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

    Generating the Top from One Description

    A miniature generator: from a written NanoSoC description to wiring, decode assigns and header defines that agree.

    Why it matters

    The value of a description is realised by the generator that consumes it: top-level wiring that connects every master to the fabric, decode logic emitted from the address assignments, and headers emitted from the same facts. When one description drives all three, an address change is a one-line edit and a rebuild instead of a treasure hunt. This lesson runs that flow in miniature on a written description of NanoSoC's core, small enough to audit by eye.

    The core idea

    The description below states, as data, the instances, who masters and who slaves, and the slave windows with bases and sizes. Three emitters read it: one lists the top-level connections, one emits the decode assigns, and one emits the header defines. The check function then does the integration work, and the script shows it can fail: every slave has exactly one window, appearing exactly once in both the decode and the header with the same base, and every instance appears in the generated wiring, so a block with no window, a window with no block, or a forgotten connection fails the build instead of floating in the chip. This is the map-table generator from the decode topic grown by one dimension: the same discipline, now covering wiring as well as addresses.

    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
    DESC = {
        "instances": [
            ("u_cpu", "nano_cpu", "master"), ("u_xbar", "nano_xbar", "fabric"),
            ("u_rom", "nano_rom", "slave"), ("u_sram0", "nano_sram0", "slave"),
            ("u_sram1", "nano_sram1", "slave"), ("u_axi2apb", "nano_axi2apb", "slave"),
        ],
        "windows": [  # slave instance, base, size (power of two, aligned)
            ("u_rom", 0x0000_0000, 0x4000), ("u_sram0", 0x2000_0000, 0x1_0000),
            ("u_sram1", 0x2001_0000, 0x1_0000), ("u_axi2apb", 0x4000_0000, 0x1_0000),
        ],
    }
    
    def emit_wiring(d):
        lines = []
        for inst, _typ, role in d["instances"]:
            if role == "master":
                lines.append(f"connect {inst}.axi -> u_xbar.m0")
            elif role == "slave":
                lines.append(f"connect u_xbar.{inst[2:]} -> {inst}.axi")
        return lines
    
    def emit_decode(d):
        lines = []
        for inst, base, size in d["windows"]:
            lo = size.bit_length() - 1
            lines.append(f"assign sel_{inst[2:]} = (addr[31:{lo}] == {(base >> lo)});")
        lines.append("assign sel_default = ~(sel_rom | sel_sram0 | sel_sram1 | sel_axi2apb);")
        return lines
    
    def emit_header(d):
        return [f"#define {inst[2:].upper()}_BASE 0x{base:08X}u" for inst, base, _s in d["windows"]]
    
    def check(d):
        problems = []
        wiring, dec, hdr = emit_wiring(d), emit_decode(d), emit_header(d)
        insts = {i for i, _t, _r in d["instances"]}
        slaves = {i for i, _t, r in d["instances"] if r == "slave"}
        connected = set()
        for w in wiring:
            left, right = w[len("connect "):].split(" -> ")
            connected |= {left.split(".")[0], right.split(".")[0]}
        for i in sorted(insts - connected):
            problems.append(f"{i}: instance never connected")
        win = [w[0] for w in d["windows"]]
        for i in sorted(slaves - set(win)):
            problems.append(f"{i}: slave has no window")
        for i in sorted(set(win) - slaves):
            problems.append(f"{i}: window for a non-slave")
        for inst, base, _size in d["windows"]:
            n = inst[2:]
            if win.count(inst) != 1 or sum(1 for l in dec if l.startswith(f"assign sel_{n} ")) != 1:
                problems.append(f"{inst}: not decoded exactly once")
            if sum(1 for l in hdr if l.startswith(f"#define {n.upper()}_BASE 0x{base:08X}u")) != 1:
                problems.append(f"{inst}: header base missing or different")
        if "sel_default" not in dec[-1]:
            problems.append("no default select")
        return problems
    
    assert check(DESC) == []
    forgot_window = {**DESC, "instances": DESC["instances"] + [("u_sram2", "nano_sram2", "slave")]}
    assert check(forgot_window) == ["u_sram2: slave has no window"]
    orphan = {**DESC, "instances": DESC["instances"] + [("u_gpio", "nano_gpio", "peripheral")]}
    assert check(orphan) == ["u_gpio: instance never connected"]
    twice = {**DESC, "windows": DESC["windows"] + [("u_rom", 0x0000_0000, 0x4000)]}
    assert any("not decoded exactly once" in p for p in check(twice))
    print("PASS: wiring, decode and header generated from one description and agree")

    Read the generated decode line for the ROM carefully: a 16 KB window puts lo at 14, so the compare is addr bits 31 down to 14 against zero, which is exactly the hand-derived equation from the decode topic. That agreement is the point of the exercise: the generator does not invent a style, it mechanises the derivation the earlier lessons did by hand, so the review burden moves from arithmetic to the description itself. Four windows fit in a lesson; the same emitters hold for forty, which is where hand derivation stops being reviewable at all.

    What the review looks for

    • The description is the only edited artifact; generated wiring, decode and headers carry do-not-edit banners and version stamps.

    • Completeness assertions run in the build: every instance connected, every window decoded and defined, no extras.

    • A sample of generated output is reviewed by eye each release, especially ties and defaults, which assertions alone cannot judge.

    Verify and debug

    Add a fifth slave to the description and regenerate: wiring, decode and header must all grow by exactly their share, and the assertions must still pass. Then remove one connection and confirm the build fails. If generated output surprises a reviewer, fix the description or the emitter, never the output; an output file with a manual patch is a fork that the next regeneration silently reverts.

    Silent tie-offs by the generator: an unconnected input quietly tied to zero by default, including, in the classic case, the upper bits of an address bus, so high addresses alias onto low memory. Generated wiring must list every tie explicitly, and the review reads the tie list first.

    If asked what you generate and what you hand-write, answer: wiring, decode, headers and documents from the description; behaviour, constraints and the description itself by hand, with assertions standing between them.

    Check yourself A new block is instantiated in the description but its window is forgotten. Which assertion catches it and when?

    The slave-versus-window check at generation time: the instance is connected, but no window names it, so the check reports a slave with no window and the build fails before any simulation runs. The script above injects exactly that case and shows it is caught.

    Related topics

    • What IP-XACT Describes

    • Next: Reviewing Generated Output

    Previous

    64 / 289

    Next: Reviewing Generated Output

    Up next

    Up nextReviewing Generated Output

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error