The DMA's LEN and KEY registers are plain read-write storage, and the designers have tested them for months. The first register bit-bash on the new register layer reports 94 errors: every bit of LEN and KEY, written and read back through the bus, "yielded" a value that differs from what the model expected. The read values are correct; the expectations are wrong. Nothing was mis-modelled. The model simply heard about each write a moment too late.
The mirror is only as good as the reports it gets. Prediction is the act of updating it after an access, and UVM gives you three ways to organise it.
Auto prediction (map.set_auto_predict(1)): the map updates the mirror inside write()/read() itself, right after the bus access completes, from what the register call asked for and the data the adapter returned. It sees only accesses made through this register model.
Explicit prediction: a uvm_reg_predictor listens to the bus monitor, decodes every observed transfer with the adapter's bus2reg(), finds the register by address and predicts it. It sees every transfer on that bus, whoever started it. Auto prediction is off by default, so explicit prediction only means wiring the predictor.
Passive prediction: explicit prediction with no register traffic of your own. The model is never connected to a sequencer for accesses; firmware on a CPU model, another master or a legacy sequence drives the bus, and the predictor keeps the mirror in step so checks and coverage still work.
An analogy: auto prediction is keeping a diary of what you said in a meeting, written the moment you finish speaking. Explicit prediction is a stenographer transcribing everything said in the room, including by people you did not invite, typing a beat behind the speaker. The diary is instant but deaf to others; the transcript is complete but always slightly behind.
The driver and the monitor both wake on the same clock edge. The driver returns the item, finish_item() returns, and write() returns to the test, all in that time step. The monitor's ap.write() also runs in that time step, but nothing orders it before the test's next statement. In this bench it lands after, so for a few delta cycles the mirror is stale.
dma_ral_env builds a uvm_reg_predictor #(apb_item), gives it the map and the adapter, and connects the APB monitor to its bus_in export. Auto prediction is a config_db knob that defaults to 0:
// snippet: from dma_ral_env in the course DMA bench (dma_ral_tb.sv)
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
regmodel.map.set_sequencer(apb.sqr, adapter);
// The explicit predictor updates the mirror from what the APB monitor sees.
// That happens when the monitor publishes the transfer - in this bench a
// moment after write()/read() return, in the same time step. Code that
// reads the mirror the instant an access returns (uvm_reg_bit_bash_seq's
// rg.get(), mirror()'s expected value) then sees the old value. Tests
// that need it turn on auto_predict as well.
regmodel.map.set_auto_predict(auto_predict);
predictor.map = regmodel.map;
predictor.adapter = adapter;
apb.ap.connect(predictor.bus_in);
endfunctionThree lines make it work: predictor.map (to look up the register by address), predictor.adapter (to decode the item) and apb.ap.connect(predictor.bus_in). Forget the connection and nothing fails: the mirror simply never changes, and the first mirror(UVM_CHECK) reports a confusing mismatch. A predictor also does more than update the mirror. If the map has check_on_read set, it compares each observed read against the mirror (lesson 5); it samples register coverage; and it publishes each predicted uvm_reg_item on its reg_ap port for anyone who wants register-level transactions.
That port is the clean feed for a register-aware scoreboard. Each item names the register (element), the access kind, the value and the status of one observed bus transfer, already decoded from address to register, so the scoreboard reasons about regmodel.key rather than address 0x0C. It fires only for traffic the predictor sees on the bus; auto prediction inside write()/read() publishes nothing there.
Mode | Updates the mirror from | Sees other masters? | When | Breaks when |
|---|---|---|---|---|
Auto | The register call and the adapter's returned data | No | Inside | Anything else writes the register: a second master, firmware, a sequence that bypasses the model |
Explicit | Monitored bus transfers | Yes, on the monitored bus | A moment after the access returns | Code reads the mirror the instant an access returns; a register has no address on that bus (lesson 7) |
Passive | Monitored transfers only | Yes | As explicit | As explicit, plus nothing in the model initiates accesses, so checks must be scheduled |
Auto and explicit | Both, one after the other | Yes | Twice per access | A side-effect callback runs twice (lesson 4 shows it); multi-beat transfers can collide |
No mode sees what the hardware does to itself. When the DMA sets DONE, counts COUNT up or a second internal engine writes a CSR, no bus transfer exists to predict. That is what volatile fields and backdoor reads are for (lessons 5 and 6). The DMA itself teaches the other limit: it ignores LEN and KEY writes while BUSY, the write still appears on the bus, and every prediction mode believes it.
The bench's own job test shows the lag in one line. It reads STATUS after the interrupt and immediately prints the mirror's DONE bit:
+UVM_TESTNAME=dma_ral_job_test
UVM_INFO dma_ral_tb.sv(346) @ 425000: uvm_test_top [RAL] STATUS=2: done=0 err=0
UVM_INFO dma_ral_tb.sv(355) @ 545000: uvm_test_top [RAL] KEY mirror=cafef00d, backdoor peek=cafef00d, status=UVM_IS_OK
UVM_INFO dma_tb.sv(411) @ 645000: uvm_test_top.env.sb [SB] 6 words matched, 0 mismatched
UVM_ERROR : 0
UVM_FATAL : 0STATUS=2 is the value read() returned, so DONE is 1 in the DUT. done=0 is the mirror, which the predictor has not updated yet. The data was right; the test was asking the wrong copy. To see the bigger effect, two scratch tests rerun the bench's bit-bash and busy-LEN tests with the auto-prediction knob forced back to 0. Each is three lines on top of the bench test:
// snippet: verified in a scratch run with the DMA bench
class s_bitbash_explicit_test extends dma_ral_bitbash_test;
`uvm_component_utils(s_bitbash_explicit_test)
function new(string name, uvm_component parent); super.new(name, parent); endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase); // sets auto_predict = 1
uvm_config_db#(bit)::set(this, "env", "auto_predict", 0); // same setter, later: this one wins
endfunction
endclassscratch run: +UVM_TESTNAME=s_bitbash_explicit_test (dma_ral_bitbash_test with auto_predict forced to 0)
UVM_INFO reg/sequences/uvm_reg_bit_bash_seq.svh(158) @ 0: reporter@@seq.reg_single_bit_bash_seq [uvm_reg_bit_bash_seq] Verifying bits in register regmodel.len in map "regmodel.map"...
UVM_ERROR reg/sequences/uvm_reg_bit_bash_seq.svh(211) @ 195000: reporter@@seq.reg_single_bit_bash_seq [uvm_reg_bit_bash_seq] Writing a 0 in bit #0 of register "regmodel.len" with initial value 'h0000000000000001 yielded 'h0000000000000000 instead of 'h0000000000000001
UVM_ERROR reg/sequences/uvm_reg_bit_bash_seq.svh(211) @ 275000: reporter@@seq.reg_single_bit_bash_seq [uvm_reg_bit_bash_seq] Writing a 1 in bit #1 of register "regmodel.len" with initial value 'h0000000000000000 yielded 'h0000000000000002 instead of 'h0000000000000000
... 92 more, one per bit write except the very first of each register: 31 on LEN, 63 on KEY
UVM_INFO dma_ral_tb.sv(316) @ 12835000: uvm_test_top [RAL] bit-bash finished for LEN, KEY and COUNT (errors, if any, are reported above)
UVM_ERROR : 94
UVM_FATAL : 0Read one error closely. Writing a 0 in bit #0 with initial value 1 yielded 0 instead of 1: the DUT returned 0, exactly what was written. The sequence takes its expected value from rg.get() right after write() returns. But the predictor's update for the previous read landed after the sequence had called write(0), and every prediction also overwrites the desired value (lesson 1), so rg.get() returned the stale 1. Only the very first write of each register escapes, because no late prediction is pending yet. Notice too that the test's own summary line is printed whatever happened. It used to say "bit-bash passed" and was reworded once this run showed it printing after 94 errors. The error count is the verdict, never a friendly info line.
scratch run: +UVM_TESTNAME=s_busy_len_explicit_test (dma_ral_busy_len_test with auto_predict forced to 0)
UVM_INFO dma_ral_tb.sv(385) @ 195000: uvm_test_top [RAL] LEN mirror after the write: 4
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 : 0
UVM_FATAL : 0This one is worse, because it is silent. The DMA ignores the LEN=9 write while BUSY, so a correct model check must fail (the bench's version, with auto prediction on, reports exactly one RegModel error). Here the mirror still said 4 when mirror(UVM_CHECK) captured its expected value, the DUT also said 4, and the check passed. A real model-versus-DUT disagreement was missed.
This is war story 11 from building the bench. The register layer was wired with explicit prediction only, the robust textbook setup. dma_ral_job_test printed done=0 after a STATUS read of 2, the bit-bash failed on every bit, and the busy-LEN check passed when it should have failed. All three had one cause: code that reads the mirror (or the desired value) the instant an access returns, in a bench where the predictor updates it a moment later.
There are three honest fixes, and the bench uses the first two:
Use the value the access returned, not the mirror. read(st, v) hands you v; compare that. The job test's COUNT check does this and is immune to the lag.
Turn on auto prediction for tests whose sequences read the mirror right away. The bench's bit-bash and busy-LEN tests set auto_predict to 1. The explicit predictor stays connected, so other masters are still seen; each access is simply predicted twice, which is harmless when both predictions agree.
Wait for the predictor. A sequence can wait a clock, or wait on the predictor's reg_ap, before reading the mirror. This keeps one prediction path but costs simulation time on every access.
Explicit (with or without auto prediction on top). The predictor decodes every transfer the monitor sees, including the firmware's. Auto prediction alone updates the mirror only for accesses made through the register model, so every firmware write leaves the mirror stale.
rm.key.write(st, v) and then rm.key.mirror(st, UVM_CHECK) on the next line. The DUT works. What can go wrong, and in which direction?mirror() captures its expected value from the mirror before its read. If the write's prediction has not landed yet, the expected value is the old KEY: the check fails on a correct DUT. If a DUT bug made the write not stick, the stale expectation can match the DUT's old value, and the check passes on a buggy DUT. Either way the check is testing timing, not the design.
The failures come from a late prediction of the previous access overwriting the desired value after the next write() has started. Before the first write of a register, no prediction for that register is pending, so rg.get() after the write returns the value just written.
Next: both prediction paths apply each field's access policy to decide the new mirror value. Field Access Policies and Callbacks shows the policies, and how to teach the model the DUT rule that broke the busy-LEN test.