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
    • IP Collateral and Versioning
    • Wrappers and Adapters
      • Why Every Block Is Wrapped
      • Width and Protocol Adapters
      • The NanoSoC Wrappers
    • Integration Lint and Connectivity Checks
    • Top-Level SDC and Constraint Merge
    • DFT Integration
    • Debug Infrastructure
    • Safety and Fault Architecture
  • Top-Level Verification and Bring-up21
  • Package, Pads and I/O18
  • Chip Signoff and Tapeout18
  • SoC Interview Prep16
  • Capstone: Integrate NanoSoC14
Loading lesson

←Wrappers and Adapters
  1. Topics
  2. SoC Design
  3. IP Integration and Handoff
  4. Wrappers and Adapters
  5. Why Every Block Is Wrapped
On this page

    Part 9 of 14, lesson 4 of 21Part 9 of 14/IP Integration and Handoff/Lesson 4 of 21All levels

    Why Every Block Is Wrapped

    The wrapper as the integrator's contract boundary: what lives inside it, what never does, and who owns each side.

    Why it matters

    The DMA's 8-bit address port inside a 12-bit APB slot is the canonical wrapper problem: somebody must drop four bits, document the sixteen alias images that result, and guarantee no slave logic depends on the dropped bits. If that logic hides inside the block, the next redelivery can change it; if it hides in the top-level wiring, no single file owns it. The wrapper is the file that owns it. The questions to settle are where that boundary runs, what the wrapper contains, what stays in the block, and why the distinction survives redelivery.

    The core idea

    The wrapper translates everything the block got wrong for this chip without changing what the block is. Address slicing and alias documentation belong here: the DMA wrapper passes apb_paddr bits 7 down to 0 and ignores bits 11 down to 8, and states the sixteen images as fact. Unused-protocol tying belongs here: the DMA has no PSTRB and no PPROT, so the wrapper ties the fabric-side expectations and documents that every write is a full 32-bit write. Clock gating and reset synchronising belong here: the DMA clock enable from CLK_EN and the synchronised rst_sys_n enter through the wrapper, because the block's reset is asynchronous-assert with no internal release synchroniser. Power isolation belongs here: the irq lines leaving the switchable domain pass wrapper cells that clamp to zero. What never belongs here is behaviour: the GO-while-busy rule, the W1C semantics, the same-cycle DONE. Behaviour stays in the block and in its tests; the wrapper only translates the boundary.

    Ownership follows the boundary. The block team owns the block and may redeliver it; the integrator owns the wrapper and reviews it as project RTL with the same lint, CDC and constraint treatment as any other project file. The checker below enforces the split mechanically. It takes the port list from the delivered module (here parsed from the module header text; in a real flow from the RTL or the register and port manifest, never from a hand-copied list) and fails on any port with no wrapper rule and on any rule that names a port the block no longer has. A redelivery that adds a port is therefore a build error rather than a silent tie.

    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
    DELIVERED_V1 = """module dma_dut (
      input  logic clk,
      input  logic rst_n,
      input  logic [7:0] paddr,
      input  logic psel,
      input  logic penable,
      input  logic pwrite,
      input  logic [31:0] pwdata,
      output logic [31:0] prdata,
      output logic pready,
      output logic pslverr,
      input  logic [31:0] s_tdata,
      input  logic s_tvalid,
      input  logic s_tlast,
      output logic s_tready,
      output logic [31:0] m_tdata,
      output logic m_tvalid,
      output logic m_tlast,
      input  logic m_tready,
      output logic irq
    );"""
    WRAPPER_RULES = {  # each block port mapped to its wrapper treatment
        "clk": "gate with CLK_EN bit 0", "rst_n": "sync release in clk_sys",
        "paddr": "slice apb_paddr[7:0], document alias A1",
        "psel": "from slot-0 select", "penable": "pass through",
        "pwrite": "pass through", "pwdata": "pass through",
        "prdata": "pass through", "pready": "pass through",
        "pslverr": "pass through",
        "s_tdata": "from sbr read engine", "s_tvalid": "from sbr",
        "s_tlast": "from sbr", "s_tready": "to sbr",
        "m_tdata": "to sbr write engine", "m_tvalid": "to sbr",
        "m_tlast": "to sbr", "m_tready": "from sbr",
        "irq": "isolate clamp-0 into clk_per domain",
    }
    
    def ports_of(rtl):
        names = []
        for line in rtl.splitlines():
            line = line.strip().rstrip(",);")
            if line.startswith(("input", "output")):
                names.append(line.split()[-1])
        return names
    
    def audit(rtl, rules):
        ports = ports_of(rtl)
        no_rule = [p for p in ports if p not in rules]
        stale = [r for r in rules if r not in ports]
        return no_rule, stale
    
    assert len(ports_of(DELIVERED_V1)) == 19
    assert audit(DELIVERED_V1, WRAPPER_RULES) == ([], [])
    assert "alias A1" in WRAPPER_RULES["paddr"]
    
    # redelivery that adds a port: the check must fail loudly
    V2 = DELIVERED_V1.replace("  output logic irq", "  output logic irq,\n  input  logic dbg_en")
    no_rule, stale = audit(V2, WRAPPER_RULES)
    assert no_rule == ["dbg_en"] and stale == [], (no_rule, stale)
    
    # redelivery that removes a port: the stale rule is reported
    V3 = DELIVERED_V1.replace("  input  logic s_tlast,\n", "")
    no_rule, stale = audit(V3, WRAPPER_RULES)
    assert no_rule == [] and stale == ["s_tlast"], (no_rule, stale)
    print("PASS: clean delivery passes; added port and removed port are each reported by name")

    What the review looks for

    • The wrapper file list with one wrapper per instantiated block, each reviewed as project RTL.

    • Every block port traced to a wrapper rule; no port tied or sliced without a comment stating why.

    • No behavioural logic in any wrapper: translators and ties only, nothing that could change a protocol outcome.

    Verify and debug

    Diff the wrapper against the block port list on every redelivery: a new or renamed port with no wrapper rule is reported by the checker above. When a block-level test passes but the integrated test fails, read the wrapper before the block; the translation is guilty until proven innocent, and the usual suspect is a sliced or tied bit whose comment describes the old revision.

    Address truncation in a bridge or adapter: a wide address narrowed silently, or an upper-address port tied off, so accesses above a boundary alias onto low memory. Symptom is high-address tests hitting the wrong slave; caught by port-width lint plus directed high-address tests; fixed by carrying the full address and checking widths in the connectivity review.

    If asked where you put a fix for a block bug found during integration, answer: in the wrapper when the block is right for its other users and only wrong for this chip, and in the block with a version bump when the behaviour is wrong everywhere. Then say how the wrapper survives redelivery: it is integrator-owned and diffed against the new port list.

    Check yourself The wrapper ties an unused block input to zero. What must accompany the tie?

    A comment with the reason and a connectivity-checker entry listing the tie explicitly. An untied-looking input tied silently is how upper address bits get grounded and high memory aliases low; the tie list is reviewed tie by tie.

    Related topics

    • Next: Width and Protocol Adapters

    • RTL: Reusable SystemVerilog for RTL

    • PD: Tie Cells and Tie-Off Hygiene

    Previous

    175 / 289

    Next: Width and Protocol Adapters

    Up next

    Up nextWidth and Protocol Adapters

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • The NanoSoC Wrappers