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
      • Why a Register Model: Desired, Mirrored and the DUT
      • Blocks, Registers, Fields and Memories
      • Maps, Offsets and Endianness
      • Nested Blocks and Submaps
      • Build, Lock and Generated Models
      • The Frontdoor Path, End to End
      • The Register Layer: Full Source
    • Register Access
    • Registers in Verification
  • Advanced UVM in Practice13
  • Debug & Regression10
  • Coding Practice47
  • Verification in Industry Practice10
  • Senior Prep: Architecture, Leadership and Interviews32
Loading lesson

‹RAL Overview
  1. Topics
  2. UVM Verification
  3. UVM Register Abstraction Layer (RAL)
  4. RAL Overview
  5. Blocks, Registers, Fields and Memories

Part 9 · Register Model (RAL) · Intermediate

Blocks, Registers, Fields and Memories

The containers of a UVM register model and the DMA's model as the worked example: every field configure() argument, access policies, volatility, reset values and memories.

On this page

    Suppose the DMA's STATUS register had been modelled as one plain RW field "to save time". A test writes 2 to clear DONE. The model predicts a write of 2 into an RW field, so its mirror now says DONE is 1. The DUT, following its spec, cleared DONE to 0. The next check of STATUS against the mirror would blame the hardware for doing the right thing. Nothing in the test was wrong; one configure() call was.

    A register model is only as good as its description of each bit. This lesson builds the DMA's model from its containers down to its fields, and explains every argument that decides how a field behaves.

    The idea: a filing cabinet with labels on every folder

    A UVM register model has four kinds of container. A block (uvm_reg_block) is one unit of hardware with registers, usually one IP; it owns its registers, memories, sub-blocks and maps. A register (uvm_reg) is one addressable word. A field (uvm_reg_field) is a named bit range inside a register, and it is where behaviour lives: width, position, access policy, reset value. A memory (uvm_mem) is an array of words with no fields and no mirror, such as a RAM or a FIFO window.

    Think of a filing cabinet. The block is the cabinet, each register a drawer at a known position, each field a folder in the drawer with a label saying who may change it and what it holds when the office opens. A memory is a drawer full of identical unlabelled sheets: you can read and write any sheet, but nobody keeps a copy of each one.

    The picture: the DMA's model

    Tree diagram: dma_reg_block owns a map and five registers: ctrl with go write-only and irq_en read-write, status with busy read-only and done and err write-1-to-clear, len with a 16-bit read-write value, key with a 32-bit read-write value, and count with a 16-bit read-only value.
    dma_reg_block owns one map and five 32-bit registers. Each register lists its fields and their access policies; offsets are the map's.

    Build it: registers and their fields

    Each register is a class derived from uvm_reg. Its constructor gives the register's width in bits (32) and whether it has coverage. Its build() creates each field with the factory and calls configure() on it. CTRL has the comment that names every argument:

    design.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
    // snippet: from the course DMA bench
    // CTRL @0x00: GO is a trigger (write 1 to start, always reads 0), IRQ_EN is RW.
    class dma_ctrl_reg extends uvm_reg;
      `uvm_object_utils(dma_ctrl_reg)
      uvm_reg_field go;
      rand uvm_reg_field irq_en;
    
      function new(string name = "dma_ctrl_reg");
        super.new(name, 32, UVM_NO_COVERAGE);
      endfunction
    
      virtual function void build();
        go     = uvm_reg_field::type_id::create("go");
        irq_en = uvm_reg_field::type_id::create("irq_en");
        //               parent size lsb access volatile reset has_reset is_rand indiv
        go.configure    (this, 1,   0,  "WO",  0,       1'b0, 1,        0,      0);
        irq_en.configure(this, 1,   1,  "RW",  0,       1'b0, 1,        1,      0);
      endfunction
    endclass
    
    // STATUS @0x04: BUSY is read-only; DONE and ERR are set by the hardware and
    // cleared by writing 1. All three change without a bus write, so all are volatile.
    class dma_status_reg extends uvm_reg;
      `uvm_object_utils(dma_status_reg)
      uvm_reg_field busy, done, err;
    
      function new(string name = "dma_status_reg");
        super.new(name, 32, UVM_NO_COVERAGE);
      endfunction
    
      virtual function void build();
        busy = uvm_reg_field::type_id::create("busy");
        done = uvm_reg_field::type_id::create("done");
        err  = uvm_reg_field::type_id::create("err");
        busy.configure(this, 1, 0, "RO",  1, 1'b0, 1, 0, 0);
        done.configure(this, 1, 1, "W1C", 1, 1'b0, 1, 0, 0);
        err.configure (this, 1, 2, "W1C", 1, 1'b0, 1, 0, 0);
      endfunction
    endclass

    Every configure() argument, with the DMA's values

    Argument

    What it means

    go

    busy

    done

    len.value

    parent

    The register that owns the field.

    this

    this

    this

    this

    size

    Width in bits.

    1

    1

    1

    16

    lsb_pos

    Position of the field's lowest bit in the register.

    0

    0

    1

    0

    access

    The policy: how writes and reads change the field (table below).

    WO

    RO

    W1C

    RW

    volatile

    1 when the hardware can change the field without a bus write. The library then turns off the mirror compare for it.

    0

    1

    1

    0

    reset

    The value after reset.

    0

    0

    0

    0

    has_reset

    0 when the field has no defined reset value; reset checks then skip it.

    1

    1

    1

    1

    is_rand

    Whether randomize() may choose a value. The library forces it to 0 for policies software cannot set, such as RO and W1C.

    0

    0

    0

    1

    individually_accessible

    Whether this field can be written on its own on the bus. It also needs an adapter with byte enables and a field that fills its byte lanes; otherwise the library warns and accesses the whole register.

    0

    0

    0

    0

    Two choices deserve a sentence. GO is WO with is_rand 0: it is a trigger, writing 1 starts a job and the bit always reads 0, so randomising it would start jobs at random. BUSY, DONE and ERR are volatile because the DMA sets and clears them itself. COUNT is volatile for the same reason. LEN and KEY are not: only software changes them.

    The block then creates each register, attaches it to itself with configure(this), builds its fields, and finally creates the map and places every register in it. The map is the next lesson's subject; here, notice that the block owns both the registers and the map, and that it calls lock_model() itself at the end.

    design.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
    // snippet: from the course DMA bench
    class dma_reg_block extends uvm_reg_block;
      `uvm_object_utils(dma_reg_block)
      rand dma_ctrl_reg   ctrl;
           dma_status_reg status;
      rand dma_len_reg    len;
      rand dma_key_reg    key;
           dma_count_reg  count;
      uvm_reg_map         map;
    
      function new(string name = "dma_reg_block");
        super.new(name, UVM_NO_COVERAGE);
      endfunction
    
      virtual function void build();
        ctrl   = dma_ctrl_reg::type_id::create("ctrl");
        status = dma_status_reg::type_id::create("status");
        len    = dma_len_reg::type_id::create("len");
        key    = dma_key_reg::type_id::create("key");
        count  = dma_count_reg::type_id::create("count");
        ctrl.configure(this);    ctrl.build();
        status.configure(this);  status.build();
        len.configure(this);     len.build();
        key.configure(this);     key.build();
        count.configure(this);   count.build();
    
        // One APB map: base 0, 4-byte bus, little endian.
        map = create_map("map", 'h0, 4, UVM_LITTLE_ENDIAN);
        map.add_reg(ctrl,   'h00, "RW");
        map.add_reg(status, 'h04, "RW");
        map.add_reg(len,    'h08, "RW");
        map.add_reg(key,    'h0C, "RW");
        map.add_reg(count,  'h10, "RO");
        lock_model();
      endfunction
    endclass

    Access policies you will meet

    Policy

    On a write of w

    On a read

    DMA example

    RW

    field = w

    no effect

    irq_en, LEN, KEY

    RO

    no effect

    no effect

    busy, COUNT

    WO

    field = w

    reads are not predicted

    go

    W1C

    each 1 in w clears that bit

    no effect

    done, err

    W1S

    each 1 in w sets that bit

    no effect

    (none)

    RC

    no effect

    field clears to 0

    (none)

    WRC

    field = w

    field clears to 0

    (none)

    W1T

    each 1 in w toggles that bit

    no effect

    (none)

    The library defines about 25 policies, including W0C, W1SRC, WO1 and NOACCESS, and uvm_reg_field::define_access() adds your own name. A policy only decides how the model predicts; side effects such as "writing CTRL.GO also sets STATUS.BUSY" need a callback, which Register Access covers.

    Memories

    The DMA has no memory, but a later revision with a descriptor RAM would add one like this. uvm_mem is created with new() (it takes the size and word width), configured with only its parent and an optional HDL path, and added to the map at an offset. A memory has no mirror: each access goes to the DUT and nothing remembers the result.

    design.sv
    1
    2
    3
    4
    5
    // snippet
    // Inside a block's build(): a 256-word descriptor RAM at offset 0x100.
    desc_ram = new("desc_ram", 256, 32, "RW", UVM_NO_COVERAGE);
    desc_ram.configure(this);
    map.add_mem(desc_ram, 'h100, "RW");

    Watch it run

    The quickest proof that the field descriptions match the RTL is the built-in reset check. dma_ral_reset_test runs uvm_reg_hw_reset_seq, which reads every register over the bus and compares each field that has a reset value with the model:

    Diagram
    UVM_INFO dma_ral_tb.sv(289) @ 155000: uvm_test_top [RAL] reset values checked (registers whose fields are all volatile are skipped)
    UVM_INFO dma_tb.sv(411) @ 155000: uvm_test_top.env.sb [SB] 0 words matched, 0 mismatched
    UVM_ERROR :    0
    UVM_FATAL :    0

    Zero errors means every reset value in a configure() call matches the hardware. Change one, for example LEN's reset to 16'h1, and this test is the one that fails. The scoreboard's "0 words" is expected: a register-only test moves no data, so it waives the empty-scoreboard check.

    What breaks: a trigger bit the model remembers

    GO's policy is honest about writes but not about the hardware's intent. After rm.ctrl.write(st, 3), the WO prediction stores GO=1 in the mirror, even though the DMA's GO reads 0 a cycle later. For a second job, the natural code is rm.ctrl.go.set(1); rm.ctrl.update(st);. update() writes only when desired differs from mirrored; both say 1, so nothing is written and the second job never starts. The bench sidesteps this by writing CTRL directly for every job, with a comment saying why.

    Two other modelling slips are worth knowing. Bits that no field covers, such as LEN's bits 31:16, are left out of the mirror compare, so a DUT that wrongly stores data there passes every check; model reserved bits as RO fields if you want them checked. And a field marked non-volatile that the hardware does change (COUNT, say) turns every mid-job mirror check into a false failure.

    Interview

    "How would you model a status register with a busy bit and a write-1-to-clear done bit?"

    A weak answer: "two fields, RO and W1C." Correct as far as it goes, and it misses the property that matters most.

    A strong answer: "Busy is RO and volatile, done is W1C and volatile, both with reset 0 and is_rand 0. Volatile matters: the hardware changes them without a bus write, so the mirror compare is off for them, and I check what read() returns against my own expectation. W1C makes the model predict a clear only for the bits I wrote as 1. If writing GO sets busy, that side effect needs a callback, because a policy only describes the field's own behaviour."

    Follow-ups: What does is_rand do on an RO field? (nothing; the library forces it off) and When would you set individually_accessible? (when the bus has byte enables and the field fills whole byte lanes, so a field write does not disturb its neighbours).

    Check yourself Why is busy configured volatile, and what does that turn off?

    The DMA sets BUSY when a job starts and clears it when the job ends, with no bus write, so the mirror cannot track it. Volatile tells the model so: the library sets the field's compare to off, so mirror(UVM_CHECK) will not report a mismatch on it, and reading its mirrored value draws a warning in the 1800.2 library.

    Check yourself A colleague configures COUNT's field with is_rand = 1 and calls rm.randomize(). What happens to COUNT?

    Nothing. COUNT is RO, and the library forces is_rand to 0 for policies software cannot set, so its value is not randomised. Only LEN, KEY and irq_en (RW, is_rand 1) take new desired values.

    Check yourself The DUT has a bug that returns garbage in LEN's bits 31:16. Does any register check catch it?

    No. No field covers bits 31:16, and the mirror compare uses only modelled bits (the valid bit mask in the RegModel error is 0xffff). Frontdoor writes drive 0 into those bits, and the reset and bit-bash sequences skip them. Model the gap as an RO field with reset 0 to get it checked.

    Next: the registers exist, but a test still needs addresses on a bus. Maps, Offsets and Endianness places them.

    Related topics

    • Maps, offsets and endianness

    • Why a register model

    • Register Access

    Previous

    148 / 280

    Next: Maps, Offsets and Endianness

    Up next

    Up nextMaps, Offsets and Endianness

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Nested Blocks and Submaps
    • Build, Lock and Generated Models
    • The Frontdoor Path, End to End