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
    • Memory Subsystem Integration
      • Macro Choice and Wrappers
      • Shared Test and Repair
      • Retention and Power Gating of SRAM
    • ECC and Error Handling
    • External Memory Controller Integration
    • Boot ROM and Boot Modes
    • eFuse and OTP
    • Secure Boot and Root of Trust
    • Security Architecture
  • 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

←Memory Subsystem Integration
  1. Topics
  2. SoC Design
  3. Memories, Boot and Security
  4. Memory Subsystem Integration
  5. Macro Choice and Wrappers
On this page

    Part 8 of 14, lesson 1 of 21Part 8 of 14/Memories, Boot and Security/Lesson 1 of 21All levels

    Macro Choice and Wrappers

    Compiler versus fixed macro, what the wrapper owns, and the checks that catch a mismatch before silicon.

    Why it matters

    This lesson has one job: choose the right memory building block and wrap it so a wrong assumption fails loudly. On NanoSoC the symptom of getting this wrong is wrong data with no error: a read latency assumed as one cycle when the macro delivers in two, or a byte-enable polarity flipped so writes land in the wrong lanes. The fabric cannot see any of it. Only the wrapper stands between the macro's truth and the interconnect's assumptions.

    The core idea

    A memory compiler generates a macro from a size and option sheet: words, bits per word, ports, mux factor, and extras such as test ports and repair rows. A fixed macro is a ready-made block with no choices. NanoSoC uses two fixed 64 KB single-port macros, one per bank, each 32 bits wide with one access per clock at 96 MHz for a peak of 384.0 MB/s per bank. The compiler route matters when the size is awkward, when the aspect ratio must fit a floorplan slot, or when low-power options such as light sleep are needed; the fixed route wins when the size is standard and the schedule is short. Either way the macro never meets the fabric directly.

    The wrapper owns five translations. It adapts the address and data widths to the fabric, applies the byte enables with the macro's polarity, registers the read path to the documented latency, multiplexes the test controller onto the macro ports during test, and routes repair and retention controls to their pins. The NanoSoC wrapper below shows the skeleton: byte-wise writes under the active-high enables, a two-cycle read pipeline, and the test-mode mux stated explicitly (repair and retention pins are left out of the snippet).

    nano_sram_wrap.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
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    // NanoSoC teaching values: single-port SRAM wrapper skeleton.
    // The two-cycle read latency, active-high enables and the test-mode mux
    // are the documented contract; change the macro, keep the contract.
    module nano_sram_wrap (
      input  logic        clk,
      input  logic        rst_n,
      input  logic        ce,
      input  logic        we,
      input  logic [13:0] addr,
      input  logic [31:0] wdata,
      input  logic [3:0]  be,
      output logic [31:0] rdata,
      input  logic        test_en,
      input  logic [13:0] test_addr,
      input  logic [31:0] test_wdata,
      input  logic        test_we,
      output logic [31:0] test_rdata
    );
      logic [13:0] m_addr;
      logic [31:0] m_wdata;
      logic        m_we;
      logic [31:0] m_rdata;
      logic [31:0] q1;
    
      logic [3:0]  m_be;
    
      assign m_addr  = test_en ? test_addr  : addr;
      assign m_wdata = test_en ? test_wdata : wdata;
      assign m_we    = test_en ? test_we    : (ce & we);
      assign m_be    = test_en ? 4'hF       : be; // the test controller writes whole words
    
      // Illustrative macro model: real macro goes here.
      logic [31:0] mem [0:16383];
      always_ff @(posedge clk) begin
        if (m_we) begin
          if (m_be[0]) mem[m_addr][7:0]   <= m_wdata[7:0];
          if (m_be[1]) mem[m_addr][15:8]  <= m_wdata[15:8];
          if (m_be[2]) mem[m_addr][23:16] <= m_wdata[23:16];
          if (m_be[3]) mem[m_addr][31:24] <= m_wdata[31:24];
        end
        q1 <= mem[m_addr];
      end
      assign m_rdata = q1;
    
      always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) rdata <= 32'h0;
        else rdata <= m_rdata;
      end
      assign test_rdata = m_rdata;
    endmodule

    The worked example

    Banking is the payoff. Each 64 KB bank sustains 361.4 MB/s on long bursts of 16, which is the 384.0 MB/s peak times the 16/17 burst efficiency. When the CPU stack in SRAM0 and a DMA stream in SRAM1 use different banks, each keeps the full rate (SRAM1 is secure-only, so this split needs the SBR's attribute cleared by secure software, or the roles swapped: DMA buffers in SRAM0 and the secure stack in SRAM1); when source and destination share one bank, the single port halves each stream to 180.7 MB/s. The script below prices three placements so the software rule earns its keep: keep DMA buffers in the other bank from the stack.

    example.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    peak = 4 * 96
    eff = 16 / 17
    sustained = peak * eff
    assert abs(sustained - 361.4) < 0.05, sustained
    split = (sustained, sustained)
    shared = (sustained / 2, sustained / 2)
    assert abs(shared[0] - 180.7) < 0.05, shared
    cases = {"split banks": split, "shared bank": shared}
    for name, (a, b) in cases.items():
        total = a + b
        print(f"{name}: {a:.1f} + {b:.1f} = {total:.1f} MB/s")
    assert abs(split[0] + split[1] - 722.8) < 0.1
    print("PASS: split banks 361.4 each, shared bank 180.7 each")

    What the review looks for

    • The wrapper document names the macro latency, enable polarity, byte-enable polarity and test ports, each with the macro datasheet line it came from.

    • No fabric signal reaches the macro except through the wrapper; the connectivity check proves it.

    • Both banks carry the same wrapper contract, so one verification environment covers both.

    Verify and debug

    Simulate against the real macro model, never a behavioural stand-in with friendlier timing: drive single reads and writes, then back-to-back bursts, and compare every beat. Then flip each polarity assumption once in a scratch copy and confirm the wrapper check flags it. A mismatch that simulation misses usually means the model was not the real one.

    SRAM wrapper mismatch: wrong read latency, flipped port or byte-enable polarity, or unconnected test and repair ports. Symptom: wrong data or a failing test with no error flag. Caught by wrapper checks run against the actual macro model. Fix: generate the wrapper from the memory compiler data and check test connectivity per instance.

    If asked compiler versus fixed macro, answer with the decision inputs: size, aspect ratio, power options and schedule. Then name the five things the wrapper owns so the macro choice never leaks into the fabric.

    Check yourself A new SRAM needs 40 KB. What size macro do you request, and what does the wrapper do with the spare?

    The map window must be a power of two, so the window is 64 KB. The macro itself may be smaller: compilers produce word counts that are not powers of two, so a 40 KB macro is possible, otherwise a 64 KB one. Either way the wrapper decodes only the bottom 40 KB as usable, and the spare part of the window answers with an error and is never aliased onto live addresses.

    Related topics

    • Next: ECC and Error Handling

    • RTL: SRAM and ROM

    • RTL: Memory Inference Starts from Coding Style

    Previous

    151 / 289

    Next: Shared Test and Repair

    Up next

    Up nextShared Test and Repair

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • Retention and Power Gating of SRAM