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

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • Start Here4
  • UVM Foundations14
  • UVM Phases & Simulation Lifecycle26
  • Factory & Configuration15
  • Transaction-Level Modeling (TLM)13
  • UVM Sequences & Stimulus21
  • UVM Agents & Protocol VIP21
  • Environment & Test Classes11
  • Checking & Coverage in UVM21
  • UVM Register Abstraction Layer (RAL)22
    • RAL Overview
    • Register Access
    • Registers in Verification
      • Built-in Register Sequences and NO_* Attributes
      • Register Coverage
      • Memories in the Register Model
      • Multiple Maps and Address Spaces
      • Shadow, Alias and Paged Registers
      • Interrupts with the Register Model
      • The Register Model at SoC Scale
      • RAL Interview Set
  • Advanced UVM in Practice13
  • Debug & Regression10
  • Coding Practice47
  • Verification in Industry Practice10
  • Senior Prep: Architecture, Leadership and Interviews32
Loading lesson

‹Registers in Verification
  1. Topics
  2. UVM Verification
  3. UVM Register Abstraction Layer (RAL)
  4. Registers in Verification
  5. Memories in the Register Model

Part 9 · Register Model (RAL) · Intermediate

Memories in the Register Model

uvm_mem without a mirror, bursts, the memory allocation manager, virtual registers, zero-time preload, and the adapter bug only a burst finds.

On this page

    A test preloads a descriptor table with burst_write, starts the engine, and the engine fetches garbage. The status of every call was UVM_IS_OK. Hours later someone turns on the APB monitor's messages and finds four bus reads where the burst should have written. The register model, the map and the driver were all correct. The adapter had a one-word bug that only bursts exercise.

    Memories are the part of the register layer people learn last and use most at chip level: descriptor rings, lookup tables, mailboxes, packet buffers. This lesson shows how uvm_mem differs from a register, how bursts, the memory allocation manager (MAM) and virtual registers work on top of it, how to preload a large memory in zero time, and what went wrong with that burst.

    The idea: a catalogue of shelves, not an inventory

    A warehouse catalogue lists the shelves: where they are, how many, how wide, who may take things from them. It does not list what is on each shelf; for that you walk to the shelf and look. A register in the model has a mirror, the model's belief about its contents. A uvm_mem has none. It records the size (number of words), the word width, the access policy ("RW" or "RO"), its offset in each map and an optional backdoor. Every read() goes to the hardware; nothing is predicted, compared or updated, and there is no mirror() or update() for a memory.

    That choice is deliberate. A model of a 1 MB buffer with a mirror per word would cost memory and time for no gain, because memory contents are usually checked by a scoreboard or by comparing against what was written. The cost is that the model cannot catch a memory that silently drops writes. If you need that, a test writes, reads back and compares, which is exactly what uvm_mem_walk_seq and uvm_mem_access_seq do.

    The scratch slave used in the next three lessons

    The DMA has no memory and one bus, so these lessons use a small slave built for scratch runs. It reuses the bench's APB agent twice, on two ports. Its SRAM array lives in an interface, the way the DMA's internal registers are exposed through dma_bd_if, so a backdoor class can reach it without HDL-path DPI.

    Port

    Offset

    What

    Behaviour

    cpu

    0x00

    CFG

    8-bit RW

    cpu

    0x04

    THRESH

    16-bit, double-buffered: writes go to a pending copy, reads return the active copy

    cpu

    0x08

    COMMIT

    bit 0 WO: copies pending THRESH to active

    cpu

    0x0C

    CFG_ALIAS

    the same flops as CFG

    cpu

    0x40–0x7C

    SRAM

    16 words of 32 bits

    dbg

    0x20 / 0x24

    CFG / THRESH

    THRESH is read-only from this port

    dbg

    0x80–0xBC

    SRAM

    the same 16 words

    The picture: where a burst goes

    Flowchart: a burst_write goes through the memory's address calculation, the map, and the adapter to four APB writes into the SRAM; dotted arrows show a MAM region, a virtual register and the backdoor all touching SRAM words.
    A burst becomes one bus access per word when the adapter has no burst support. MAM regions and virtual registers are views of memory words; the backdoor reaches the array directly.

    Build it: a 16-word SRAM in the model

    Construct, configure, map

    uvm_mem has no factory registration in the library and its constructor takes the geometry, so it is built with new(name, size, n_bits, access, has_coverage) and then configure(parent, hdl_path). The order matters: configure attaches it to the block, and add_mem gives it an offset in a map, in bytes.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    // snippet: from the scratch block's build()
    sram = new("sram", 16, 32, "RW", UVM_NO_COVERAGE);   // 16 words of 32 bits
    sram.configure(this);                                // no HDL path: a user backdoor instead
    cpu_map = create_map("cpu_map", 'h0, 4, UVM_LITTLE_ENDIAN);
    cpu_map.add_mem(sram, 'h40);                         // words at 0x40, 0x44, ... 0x7C
    // ... registers added as usual ...
    lock_model();

    A memory backdoor is the same uvm_reg_backdoor class the bench uses for KEY and LEN, but rw.offset now says which word. Writes are allowed here, because preloading is the point:

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // snippet: scratch memory backdoor
    class sram_backdoor extends uvm_reg_backdoor;
      virtual sram_store_if vif;                 // the interface that holds the array
      function new(string name = "sram_backdoor");
        super.new(name);
      endfunction
      virtual function void read_func(uvm_reg_item rw);
        rw.value[0] = vif.mem[rw.offset];
        rw.status   = UVM_IS_OK;
      endfunction
      virtual task write(uvm_reg_item rw);
        vif.mem[rw.offset] = rw.value[0];
        rw.status = UVM_IS_OK;
      endtask
    endclass
    // in the env: rm.sram.set_backdoor(bd);

    Single words, bursts, and the backdoor

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    // snippet: scratch test
    uvm_reg_data_t wr[] = '{32'hA0, 32'hA1, 32'hA2, 32'hA3}, rd[];
    rm.sram.burst_write(st, 0, wr);                 // words 0..3
    rd = new[4];
    rm.sram.burst_read(st, 0, rd);
    for (int i = 12; i < 16; i++)
      rm.sram.poke(st, i, 32'hC0DE_0000 + i);        // preload: no bus, no time
    rm.sram.read(st, 13, v);                         // frontdoor read of a poked word

    A burst is one call and one uvm_reg_item with an array of values. The map turns it into bus operations. If the adapter cannot express a burst, which is true of APB and of the bench's adapter, it becomes one access per word, each with the operation kind UVM_BURST_WRITE or UVM_BURST_READ. That detail is the war story below.

    Tip

    For a large memory, the fastest preload is not poke() in a loop but $readmemh straight into the RTL array, or one backdoor task that copies a whole image. Each poke() builds a uvm_reg_item and runs callbacks. Since a memory has no mirror, the model needs no update afterwards, which makes zero-time preload safe in a way it is not for registers.

    Carving a memory up: MAM regions

    When several sequences share one memory (a descriptor ring, packet buffers, a mailbox), they need non-overlapping pieces. The memory allocation manager, uvm_mem_mam, hands out regions. reserve_region(offset, n_bytes) claims a fixed range; request_region(n_bytes) finds a free one using the configured policy. Sizes are in bytes and rounded up to whole words; region offsets are in words.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    // snippet: scratch test
    uvm_mem_mam_cfg mcfg = new;
    mcfg.n_bytes = 4;  mcfg.start_offset = 0;  mcfg.end_offset = 15;
    mcfg.mode = uvm_mem_mam::GREEDY;  mcfg.locality = uvm_mem_mam::BROAD;
    mam   = new("mam", mcfg, rm.sram);
    ring  = mam.reserve_region(0, 4*4);       // words 0..3, fixed
    buf_r = mam.request_region(2*4);          // any 2 free words
    buf_r.write(st, 1, 32'hBEEF);             // offset 1 inside the region

    Registers laid over memory: uvm_vreg

    Some memories hold an array of structures, such as a table of descriptors. A virtual register (uvm_vreg) gives each entry named fields without a mirror: desc.write(2, ...) writes entry 2, and desc.len.write(1, 7) writes one field of entry 1 by reading the word, merging and writing it back. Virtual fields are bit slices like register fields, but with no access policy and no prediction.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // snippet: scratch virtual register over SRAM words 8..11
    class desc_vreg extends uvm_vreg;
      rand uvm_vreg_field addr, len;
      function new(string name = "desc_vreg");
        super.new(name, 32);
      endfunction
      function void build();
        addr = new("addr");  addr.configure(this, 16, 0);
        len  = new("len");   len.configure(this, 16, 16);
      endfunction
    endclass
    // in the block's build(), before lock_model():
    desc = new("desc");
    desc.configure(this, sram, 4, 'h8, 1);   // 4 entries from word 8, one word apart
    desc.build();

    Watch it run

    Diagram
    scratch run, Verilator
    UVM_INFO mini_tb.sv(328) @ 215000: uvm_test_top [MEM] burst_write status UVM_IS_OK at 215000
    UVM_INFO mini_tb.sv(336) @ 335000: uvm_test_top [MEM] burst_read: '{'ha0, 'ha1, 'ha2, 'ha3}, status UVM_IS_OK at 335000
    UVM_INFO mini_tb.sv(344) @ 335000: uvm_test_top [MEM] ring ['h0000000000000000:'h0000000000000003], buffer ['h000000000000000c:'h000000000000000d]
    UVM_INFO mini_tb.sv(348) @ 366000: uvm_test_top [MEM] buffer word 1 is sram[13] = beef (peek)
    UVM_INFO reg/uvm_vreg.svh(1018) @ 395000: reporter [RegModel] Wrote virtual register "desc"[2] via frontdoor with: 'h0000000000030040
    UVM_INFO reg/uvm_vreg_field.svh(521) @ 425000: reporter [RegModel] Wrote virtual field "desc.len"[1] via frontdoor with: 'h0000000000070000
    UVM_INFO mini_tb.sv(353) @ 455000: uvm_test_top [MEM] desc[2] is sram[10] = 00030040
    UVM_INFO mini_tb.sv(355) @ 485000: uvm_test_top [MEM] desc[1] after len.write(1,7) is sram[9] = 00070000
    UVM_INFO mini_tb.sv(359) @ 515000: uvm_test_top [MEM] frontdoor read of poked sram[13] = c0de000d at 515000
    UVM_INFO reg/sequences/uvm_mem_walk_seq.svh(138) @ 515000: reporter@@walk.single_mem_walk_seq [uvm_mem_walk_seq] Walking memory rm.sram in map "rm.cpu_map"...
    UVM_INFO reg/sequences/uvm_mem_walk_seq.svh(138) @ 1925000: reporter@@walk.single_mem_walk_seq [uvm_mem_walk_seq] Walking memory rm.sram in map "rm.dbg_map"...
    UVM_ERROR :    0
    • The burst took 120 ns: four APB writes of 30 ns each (this slave has no wait states). The burst is a convenience for the test, not a faster bus.

    • request_region placed the buffer at words 12–13; the policy chooses randomly among free ranges, so another seed gives another place. Code that hard-codes where a requested region landed breaks on the next seed.

    • The poke loop at words 12–15 ran in zero time and overwrote the MAM buffer's word 13 (BEEF became C0DE000D). The MAM only manages requests made through it; a backdoor write ignores it. Preload through the regions, or reserve the preloaded range first.

    • uvm_mem_walk_seq walked the memory once per map: the SRAM is in both the CPU and the debug maps, so it is tested through both ports.

    What breaks: the burst that read

    The first run of this scratch test used the bench's apb_reg_adapter unchanged. Every call returned UVM_IS_OK, the burst read returned four zeros, and the APB monitor (at UVM_HIGH) showed what really happened during the burst write:

    Diagram
    UVM_INFO dma_tb.sv(151) @ 125000: uvm_test_top.env.cpu.mon [APB_MON] RD addr=40 rdata=00000000
    UVM_INFO dma_tb.sv(151) @ 155000: uvm_test_top.env.cpu.mon [APB_MON] RD addr=44 rdata=00000000
    UVM_INFO dma_tb.sv(151) @ 185000: uvm_test_top.env.cpu.mon [APB_MON] RD addr=48 rdata=00000000
    UVM_INFO dma_tb.sv(151) @ 215000: uvm_test_top.env.cpu.mon [APB_MON] RD addr=4c rdata=00000000

    The bench adapter's reg2bus used to set it.write = (rw.kind == UVM_WRITE). For single accesses that is right. For a burst, the map passes each per-word operation with kind = UVM_BURST_WRITE, the comparison is false, and the adapter builds a read. Nothing in the register layer checks the direction of the item an adapter returns, and a read of a fresh SRAM returns zeros, so no error appears anywhere. The fix is one line:

    design.sv
    // snippet: reg2bus that handles burst operations
    it.write = (rw.kind inside {UVM_WRITE, UVM_BURST_WRITE});

    The bench never called a burst, so its adapter looked correct, and that is exactly why the bug survives in real projects. This run found it, and the course bench now uses the fixed line. In general: an adapter is written for registers, signed off with bit-bash and reset tests, and meets its first burst months later at chip level. Add a burst write and read-back to the adapter's own smoke test.

    Common pitfall

    A second timing trap showed up in the same run. A peek() issued in the same time step that burst_write returned read 0 for word 3; 1 ns later it read A3. The driver finishes on the clock edge, before the slave's nonblocking assignment to the array has landed. A backdoor read straight after a frontdoor write can see the old value; wait for the next edge (or any delay) before comparing.

    Interview

    "How is a uvm_mem different from a uvm_reg, and how do you verify a large memory?"

    A weak answer: "A memory is an array of registers; you read and write it the same way and check the mirror."

    A strong answer: "A uvm_mem has geometry, access and offsets, but no mirror, so there is no prediction, mirror() or update(). Contents are checked by reading back, by a scoreboard, or by the built-in walk and access sequences, which need a backdoor for the access test. It is built with new(name, size, n_bits, access, coverage) and configure(parent, hdl_path). For a large memory I preload by backdoor or $readmemh in zero time, use the MAM so sequences get non-overlapping regions, and use virtual registers for tables of structures. And I make sure the adapter handles UVM_BURST_WRITE and UVM_BURST_READ, because a burst arrives with those kinds."

    Follow-ups: Why does the MAM not protect against a backdoor preload? (it only tracks regions requested through it). Your burst writes report OK but the memory is empty. First check? (the bus monitor: did writes go out, and did the adapter map the burst kind).

    Check yourself A test calls rm.sram.mirror(...) after a burst write. What happens, and what should the test do instead?

    There is no mirror() on uvm_mem; memories keep no model copy, so the call does not compile. To check contents, read the words back (frontdoor read/burst_read, or peek through the backdoor) and compare with what was written, or let a scoreboard compare the engine's output.

    Check yourself Two sequences each call mam.request_region(64) on the same MAM. Can they overlap? What if one of them also preloads with poke()?

    Requests through one MAM never overlap: each region is recorded and later requests avoid it. A poke() bypasses the MAM entirely, so a backdoor preload can land on a region another sequence was given, as the scratch run shows. Preload through the region objects or reserve the preload range with reserve_region first.

    Check yourself desc.len.write(1, 7) changed only the upper 16 bits of SRAM word 9. How did the virtual field know the lower 16 bits?

    A virtual field write is a read-modify-write: the library reads the entry's word through the frontdoor, replaces the field's bits and writes the word back. Virtual registers have no mirror, so the read is real bus traffic. That also means a virtual field write is not atomic against other masters writing the same word.

    Next: the scratch SRAM is reachable from two ports, and so are its registers. Multiple Maps and Address Spaces shows how one register lives in two maps without the mirror missing the other bus.

    Related topics

    • Up: Registers in Verification

    • Next: Multiple Maps and Address Spaces

    • Blocks, registers, fields and memories

    Previous

    163 / 280

    Next: Multiple Maps and Address Spaces

    Up next

    Up nextMultiple Maps and Address Spaces

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Built-in Register Sequences and NO_* Attributes
    • Shadow, Alias and Paged Registers
    • Interrupts with the Register Model