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
      • The Access API: write, read, set, get, update, mirror, predict, peek, poke
      • The Adapter Contract: reg2bus, bus2reg and provides_responses
      • Prediction: Auto, Explicit and Passive
      • Field Access Policies and Callbacks
      • Volatile Fields and Mirror Checks
      • Backdoor Access: HDL Paths and User Backdoors
      • User-Defined Frontdoors: Indirect and Paged Registers
    • Registers in Verification
  • Advanced UVM in Practice13
  • Debug & Regression10
  • Coding Practice47
  • Verification in Industry Practice10
  • Senior Prep: Architecture, Leadership and Interviews32
Loading lesson

‹Register Access
  1. Topics
  2. UVM Verification
  3. UVM Register Abstraction Layer (RAL)
  4. Register Access
  5. Volatile Fields and Mirror Checks

Part 9 · Register Model (RAL) · Intermediate

Volatile Fields and Mirror Checks

What volatile changes, mirror(UVM_CHECK) versus check_on_read, the compare mask, the busy-LEN mismatch, and the backdoor peek that hid it.

On this page

    A team adds a tidy end-of-test check: for every register in the DMA, call mirror(status, UVM_CHECK). The first run fails on STATUS and COUNT in every test that ran a job, even though the scoreboard matched every word. The check is comparing the model's last belief with registers the hardware updates on its own. Then someone "fixes" it by peeking every register first, and the check never fails again, including on the day the DUT really does disagree with the model.

    The idea: some fields cannot be predicted from the bus

    A volatile field is one whose value can change without any bus access: a status bit set by hardware, a counter, a FIFO level, a BUSY flag. A mirror is a prediction built from bus traffic, so for a volatile field it goes stale the moment the hardware moves. A weather forecast printed this morning is the right analogy: useful as a starting point, wrong to compare against the sky this afternoon.

    Declaring a field volatile (the fifth argument of configure()) changes three things in the library:

    • Its compare is switched off. configure() sets the field's check to UVM_NO_CHECK, so mirror compares skip its bits. set_compare() can switch it back (lesson 4).

    • `needs_update()` is always true for it if it is writable, so update() always writes its register (lesson 4 showed what that does to W1C bits).

    • Reading its mirror warns in the IEEE 1800.2-2020 library: get_mirrored_value() reports UVM/FLD/GET_MIRRORED_VAL/VOL, saying the value may not be current. UVM 1.2 has no such warning.

    Prediction still runs for volatile fields: a frontdoor read or a peek updates their mirror like any other. What changes is that nothing trusts that mirror.

    The picture: what one mirror check compares

    Flowchart: mirror with UVM_CHECK captures the mirror as the expected value, reads the register, builds a mask that skips volatile, write-only and unmodelled bits, compares, reports a RegModel error on a mismatch, and finally predicts the read value into the mirror.
    mirror() with UVM_CHECK. The expected value is the mirror before the read, the mask drops every bit the model cannot vouch for, and the read value is predicted into the mirror either way.

    Two details in this picture decide whether a check is worth anything. The expected value is captured before the read, so it is whatever the mirror held at that instant. And after the compare, the read value is predicted into the mirror by whichever prediction mode is active, mismatch or not: a failing check also repairs the mirror, so the same check run twice fails only once.

    Build it: the three ways to check

    Mechanism

    When it compares

    Default

    mirror(st, UVM_CHECK)

    Once, on this read

    mirror() alone is UVM_NO_CHECK: it just refreshes the mirror

    map.set_check_on_read(1)

    On every read through that map

    Off

    Compare the returned value yourself

    Wherever your test decides

    -

    set_check_on_read(1) turns every read() into a check. Where it runs depends on the prediction mode: with auto prediction, inside the map's read; with the explicit predictor, in the predictor when it sees the read on the bus; with both on, in both places, so one mismatch is reported twice. A read() with UVM_BACKDOOR checks too, inside the register's own read() against the map's check_on_read; a peek() never does. The bit mask is the same as for mirror().

    For volatile fields, compare the returned value against an expectation you can defend. The bench's job test does exactly that for COUNT, which is volatile: it uses the value read() returned and the job length it programmed, not the mirror:

    design.sv
    // snippet: from dma_ral_job_test in the course DMA bench (dma_ral_tb.sv)
    rm.count.read(st, v);
    if (v != 6) `uvm_error("RAL", $sformatf("COUNT=%0d, expected 6", v))

    At a moment where you do know a volatile value, such as COUNT after DONE with the DMA idle, you can make the model check it anyway: switch the field's compare on, predict the value you expect, and mirror with a check. This is a pattern, not bench code:

    design.sv
    1
    2
    3
    4
    5
    // snippet: checking a volatile field at a quiet point
    rm.count.value.set_compare(UVM_CHECK);   // volatile fields start as UVM_NO_CHECK
    void'(rm.count.predict(6));               // what the job must have left behind
    rm.count.mirror(st, UVM_CHECK);           // reads COUNT; any difference is a RegModel error
    rm.count.value.set_compare(UVM_NO_CHECK);

    Bits that no field models are never compared either (fact 38 of the fact sheet). The DMA's STATUS register has 29 such bits; they can read back anything without a mismatch. Model reserved bits only if you want them checked.

    Watch it run

    The bench's busy-LEN test is the model check doing its job. LEN is not volatile, the DMA ignores the write of 9 while BUSY, and nothing in the model knows that rule:

    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
    // snippet: from the course DMA bench
    // The model meets a DUT rule it does not know: the DMA ignores writes to LEN
    // while BUSY. The write goes out on the bus, the predictor updates the mirror,
    // and a mirror(UVM_CHECK) read then reports the DUT's real value as a mismatch.
    // This test is expected to end with one UVM_ERROR from the register model.
    class dma_ral_busy_len_test extends dma_ral_base_test;
      `uvm_component_utils(dma_ral_busy_len_test)
      function new(string name, uvm_component parent);
        super.new(name, parent);
      endfunction
      function void build_phase(uvm_phase phase);
        uvm_config_db#(bit)::set(this, "env", "auto_predict", 1);  // mirror() takes its expected value at once
        super.build_phase(phase);
      endfunction
      task run_phase(uvm_phase phase);
        uvm_status_e   st;
        uvm_reg_data_t peeked;
        dma_reg_block  rm  = renv.regmodel;
        axis_pkt_seq   pkt = axis_pkt_seq::type_id::create("pkt");
        phase.raise_objection(this);
        rm.key.write(st, 32'h0000_00FF);
        rm.len.write(st, 4);
        rm.ctrl.write(st, 32'h3);                 // the DMA is now BUSY
        rm.len.write(st, 9);                      // ignored by the DMA, believed by the mirror
        `uvm_info("RAL", $sformatf("LEN mirror after the write: %0d", rm.len.get_mirrored_value()), UVM_LOW)
        rm.len.mirror(st, UVM_CHECK);             // reads 4, expects 9: RegModel error
        // peek() after the check, not before: a peek predicts the value it reads,
        // so peeking first would quietly set the mirror to 4 and hide the mismatch.
        rm.len.peek(st, peeked);
        `uvm_info("RAL", $sformatf("DUT LEN (backdoor peek)=%0d, mirror now %0d", peeked, rm.len.get_mirrored_value()), UVM_LOW)
        if (!pkt.randomize() with { n == 4; }) `uvm_fatal("RAND", "axis_pkt_seq randomize failed")
        pkt.start(env.in_agt.sqr);
        wait (irq_vif.irq === 1'b1);
        rm.status.write(st, 32'h2);
        #100ns;
        phase.drop_objection(this);
      endtask
    endclass
    Diagram
    +UVM_TESTNAME=dma_ral_busy_len_test
    UVM_INFO dma_ral_tb.sv(385) @ 195000: uvm_test_top [RAL] LEN mirror after the write: 9
    UVM_ERROR reg/uvm_reg.svh(2812) @ 235000: reporter [RegModel] Register "regmodel.len" value read from DUT (0x0000000000000004) does not match mirrored value (0x0000000000000009) (valid bit mask = 0x000000000000ffff)
    UVM_INFO reg/uvm_reg.svh(2833) @ 235000: reporter [RegModel] Field value (regmodel.len[15:0]) mismatch read=16'h4 mirrored=16'h9
    UVM_INFO dma_ral_tb.sv(390) @ 235000: uvm_test_top [RAL] DUT LEN (backdoor peek)=4, mirror now 4
    UVM_INFO dma_tb.sv(411) @ 515000: uvm_test_top.env.sb [SB] 4 words matched, 0 mismatched
    UVM_ERROR :    1
    UVM_FATAL :    0
    • `LEN mirror after the write: 9`: the model believes the write. This test runs with auto prediction on, so the belief is immediate (lesson 3).

    • `valid bit mask = 0x...ffff`: only LEN's 16 modelled bits are compared.

    • One `UVM_ERROR`, expected by design, followed by a per-field line naming regmodel.len[15:0]. That second line is the one to read in a register with many fields.

    • `mirror now 4`: the failing check predicted the read value, as the picture showed.

    The volatile warnings appear in the job test, which reads STATUS's mirror right after a read. They are worth reading rather than filtering:

    Diagram
    +UVM_TESTNAME=dma_ral_job_test   (the same run as above, warnings kept)
    UVM_WARNING reg/uvm_reg_field.svh(1327) @ 425000: reporter [UVM/FLD/GET_MIRRORED_VAL/VOL] Mirrored value returned for volatile field "regmodel.status.done" may not reflect the actual current value.
    UVM_WARNING reg/uvm_reg_field.svh(1327) @ 425000: reporter [UVM/FLD/GET_MIRRORED_VAL/VOL] Mirrored value returned for volatile field "regmodel.status.err" may not reflect the actual current value.
    UVM_INFO dma_ral_tb.sv(346) @ 425000: uvm_test_top [RAL] STATUS=2: done=0 err=0

    The library is saying exactly what the done=0 line proves: a volatile field's mirror is not a source of truth. (Here the mirror is also behind because of explicit prediction; for a volatile field it would be untrustworthy even without the lag.)

    What breaks: a peek that hid a real mismatch

    This is war story 12 from the bench. An early version of the busy-LEN test peeked LEN before the check, to print the DUT value next to the mirror. The check then passed and the test reported no error at all. A scratch run reproduces it by swapping two lines:

    Diagram
    scratch run: +UVM_TESTNAME=s_peek_first_test (the busy-LEN test with peek() moved before the check)
    UVM_INFO sacc_tb.sv(272) @ 195000: uvm_test_top [RAL] LEN mirror after the write: 9
    UVM_INFO sacc_tb.sv(274) @ 195000: uvm_test_top [RAL] DUT LEN (backdoor peek)=4, mirror now 4
    UVM_INFO sacc_tb.sv(276) @ 235000: uvm_test_top [RAL] mirror(UVM_CHECK) finished
    UVM_INFO dma_tb.sv(411) @ 625000: uvm_test_top.env.sb [SB] 4 words matched, 0 mismatched
    UVM_ERROR :    0
    UVM_FATAL :    0

    A peek() is a backdoor read, and like every read it predicts the value it read. The peek set the mirror to the DUT's 4. mirror(UVM_CHECK) then captured 4 as its expected value, read 4 and passed. The model gap (no "ignored while BUSY" rule) was still there; the test had simply overwritten the evidence before comparing it. The bench's version runs the check first and peeks afterwards, with a comment saying why.

    The general rule: never let any access to a register run between the moment you form an expectation and the moment you check it. That includes reads for debug prints, peeks, mirror() without a check, and other sequences running in parallel. The end-of-test "peek everything, then mirror-check everything" idea from the opening fails for the same reason: it makes every check compare the DUT with itself.

    Interview

    "`mirror(UVM_CHECK)` fails on a status register. What do you check?"

    A weak answer: "Mark the register volatile so the error goes away."

    A strong answer: "First, whether the field really is changed by hardware. If it is (status, counters, BUSY), it should be volatile, and I check it against an expectation from the scoreboard, not the mirror. If it is not, the error may be real: either a DUT bug, or a model gap such as a write the DUT ignores in some state, which I would model with a callback. Then I look at prediction: is the predictor connected, and is the mirror stale because of explicit prediction lag, or because another master wrote it? Finally, I make sure nothing peeked or read the register between forming the expectation and the check, because a peek predicts what it reads."

    Follow-ups: What does volatile change besides the compare? (needs_update() is always true for writable volatile fields, and the 2020 library warns on get_mirrored_value().) What does `check_on_read` do with both prediction modes on? (It checks in the map and in the predictor, so a mismatch is reported twice.)

    Check yourself A FIFO level register is RO and modelled as non-volatile. With check_on_read on, it fails intermittently. Why, and what are the two reasonable fixes?

    The hardware changes the level without bus writes, so the mirror (last read or reset value) is stale whenever the FIFO moved. Mark the field volatile, which turns its compare off, and check the level against a scoreboard expectation where one exists. Alternatively keep it non-volatile but set_compare(UVM_NO_CHECK); volatile is the more honest model.

    Check yourself A test calls rm.len.mirror(st, UVM_CHECK) twice in a row while LEN differs from the mirror. How many errors appear?

    One (with prediction working, as in the bench's busy-LEN setup). The first check reports the mismatch, and its read is then predicted into the mirror, so the second check compares the DUT with itself and passes.

    Check yourself Why is a get_mirrored_value() warning on a volatile field useful rather than noise?

    It flags code that treats a stale prediction as the truth. In the bench, the warning sits next to the line printing done=0 after STATUS read 2. The fix is to use the value read() returned, which is what the warning is pointing at.

    Next: peek() turned out to have side effects on the model. Backdoor Access explains how backdoor access reaches the DUT, what it predicts, and the race it hides.

    Related topics

    • Up: Register Access

    • Predictor design

    • RAL and checking interview

    Previous

    158 / 280

    Next: Backdoor Access: HDL Paths and User Backdoors

    Up next

    Up nextBackdoor Access: HDL Paths and User Backdoors

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • The Access API: write, read, set, get, update, mirror, predict, peek, poke
    • The Adapter Contract: reg2bus, bus2reg and provides_responses
    • Prediction: Auto, Explicit and Passive