The DMA's reset test was green, and its last line used to be reassuring: hardware reset values checked for every register. In a design review someone read the three lines above it. The sequence verified CTRL, LEN and KEY. STATUS and COUNT, two of the five registers, were never read. Nobody excluded them, nothing warned, and the message that said "every register" was written by a person, not by the library. (The bench's message now says what really happened: registers whose fields are all volatile are skipped.)
The built-in register sequences are the cheapest tests in a DV plan: a few lines of code each, generated from the register model, run on every block. This lesson shows what each one really does, which registers and bits it silently skips, how you exclude the ones it must not touch, and why one of them failed on every bit of the DMA until the prediction setup changed.
A building inspector arrives with a standard checklist: every door opens, every switch turns its light on, every tap runs. The checklist is generated from the floor plan, so it is only as good as the plan, and the inspector never asks whether the building is any good as a hospital. The built-in sequences are that inspector. They read the register model (the floor plan) and check that the hardware agrees with it: reset values, read/write paths for each bit, frontdoor against backdoor. They know nothing about what the DMA does.
Two facts decide what they check. A field whose get_compare() is UVM_NO_CHECK is skipped, and configuring a field as volatile sets exactly that: uvm_reg_field::configure stores m_check = volatile ? UVM_NO_CHECK : UVM_CHECK. STATUS (BUSY, DONE, ERR) and COUNT are volatile, so the reset sequence finds nothing to compare in them and moves on without a word. The second fact: bits the model does not describe are treated as read-only zeros by bit-bash, so they are checked, whether you meant it or not.
Sequence | What it does, per register (or memory) and map | What it skips or needs |
|---|---|---|
|
| Fields with no reset or with |
| For each bit: write it flipped, read back, compare; then flip it back | Bits of WO, WOC, WOS, WO1, NOACCESS and |
| Frontdoor write, backdoor check; backdoor write, frontdoor check | Needs a backdoor ( |
| Walking-pattern write and read of every word | RW memories only |
| Frontdoor against backdoor for memory words | Needs a memory backdoor |
| A write through one map is visible through every other map | Registers and memories in more than one map |
| Runs the ones selected by its | Each sub-sequence keeps its own skips |
Read the dotted arrows as filters that the sequences consult, not switches you flip on the sequence. Each sequence calls uvm_resource_db#(bit)::get_by_name({"REG::", element.get_full_name()}, "NO_...", 0) and skips the element if a resource exists at all. The value is never read, so setting an attribute to 0 still disables the test. There are also narrower names: NO_REG_SHARED_ACCESS_TEST, NO_MEM_WALK_TEST, NO_MEM_ACCESS_TEST, and NO_REG_HW_RESET_TEST on a single field to drop just that field from the reset check.
The test hands the sequence the model and starts it with no sequencer: register sequences reach the bus through the map's sequencer, so start(null) is the normal way to run them. The test waives the scoreboard's empty-compare error because it moves no data.
// snippet: from the course DMA bench
// Every register's reset value, read through the bus and checked against the model.
class dma_ral_reset_test extends dma_ral_base_test;
`uvm_component_utils(dma_ral_reset_test)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void end_of_elaboration_phase(uvm_phase phase);
super.end_of_elaboration_phase(phase);
waive_empty_scoreboard();
endfunction
task run_phase(uvm_phase phase);
uvm_reg_hw_reset_seq seq = uvm_reg_hw_reset_seq::type_id::create("seq");
phase.raise_objection(this);
seq.model = renv.regmodel;
seq.start(null);
`uvm_info("RAL", "reset values checked (registers whose fields are all volatile are skipped)", UVM_LOW)
phase.drop_objection(this);
endtask
endclassIts log is the one from the opening. CTRL is checked (IRQ_EN; GO is WO and is left out of the compare), and so are LEN and KEY. STATUS and COUNT are missing because every field in them is volatile. That is correct behaviour, not a bug: a volatile field may legally change between reset and the read, so there is no safe expected value. But it means the reset values of BUSY, DONE, ERR and COUNT need a directed check if your plan lists them, for example one read() right after reset compared with 0 in the test itself.
// snippet: from the course DMA bench
// Walk a 1 and a 0 through every bit of the RW registers. CTRL and STATUS are
// excluded: writing CTRL.GO would start a job, and STATUS bits are W1C.
class dma_ral_bitbash_test extends dma_ral_base_test;
`uvm_component_utils(dma_ral_bitbash_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); // bit-bash reads rg.get() right after write()
super.build_phase(phase);
endfunction
function void end_of_elaboration_phase(uvm_phase phase);
super.end_of_elaboration_phase(phase);
waive_empty_scoreboard();
endfunction
task run_phase(uvm_phase phase);
uvm_reg_bit_bash_seq seq = uvm_reg_bit_bash_seq::type_id::create("seq");
phase.raise_objection(this);
uvm_resource_db#(bit)::set({"REG::", renv.regmodel.ctrl.get_full_name()}, "NO_REG_TESTS", 1, this);
uvm_resource_db#(bit)::set({"REG::", renv.regmodel.status.get_full_name()}, "NO_REG_TESTS", 1, this);
seq.model = renv.regmodel;
seq.start(null);
`uvm_info("RAL", "bit-bash finished for LEN, KEY and COUNT (errors, if any, are reported above)", UVM_LOW)
phase.drop_objection(this);
endtask
endclassTwo registers are excluded, each for its own reason. CTRL: bit-bash writes every bit pattern, and a write with bit 0 set starts a DMA job. The GO bit is WO, so bit-bash would not even compare it; the harm is the side effect of the write, not the check. STATUS: DONE and ERR are write-1-to-clear and volatile. Bit-bash could not compare them anyway, and its writes would clear status bits other checks rely on. Excluding a register with NO_REG_TESTS says why in one line, and it also keeps the reset and access sequences off it.
+UVM_TESTNAME=dma_ral_reset_test (run.sh dma_ral_tb.sv)
UVM_INFO reg/sequences/uvm_reg_hw_reset_seq.svh(153) @ 0: reporter@@seq [uvm_reg_hw_reset_seq] Verifying reset value of register regmodel.ctrl in map "regmodel.map"...
UVM_INFO reg/sequences/uvm_reg_hw_reset_seq.svh(153) @ 75000: reporter@@seq [uvm_reg_hw_reset_seq] Verifying reset value of register regmodel.len in map "regmodel.map"...
UVM_INFO reg/sequences/uvm_reg_hw_reset_seq.svh(153) @ 115000: reporter@@seq [uvm_reg_hw_reset_seq] Verifying reset value of register regmodel.key in map "regmodel.map"...
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+UVM_TESTNAME=dma_ral_bitbash_test (run.sh dma_ral_tb.sv)
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_INFO reg/sequences/uvm_reg_bit_bash_seq.svh(158) @ 5155000: reporter@@seq.reg_single_bit_bash_seq [uvm_reg_bit_bash_seq] Verifying bits in register regmodel.key in map "regmodel.map"...
UVM_INFO reg/sequences/uvm_reg_bit_bash_seq.svh(158) @ 10275000: reporter@@seq.reg_single_bit_bash_seq [uvm_reg_bit_bash_seq] Verifying bits in register regmodel.count in map "regmodel.map"...
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_INFO dma_tb.sv(411) @ 12835000: uvm_test_top.env.sb [SB] 0 words matched, 0 mismatched
UVM_ERROR : 0
UVM_FATAL : 0The bit-bash timestamps tell you how many bits were bashed. Each bit costs two writes and two reads at 40 ns per APB access (the DMA adds one wait state), so 160 ns per bit. LEN took 5.12 µs: 32 bits, its 16 field bits and the 16 unmodelled upper bits, which must read 0.
COUNT took 2.56 µs: 16 bits. Its only field is RO and volatile, so all 16 field bits are skipped, and what bit-bash really checked on COUNT is that bits 31:16 read 0. Zero errors on COUNT is true, but it means less than it sounds.
Random values and timestamps differ by simulator. The bit counts do not.
The same choices can be made in one place with the wrapper sequence. A scratch test (not part of the bench) runs reset, bit-bash and access from uvm_reg_mem_built_in_seq with three attributes: no tests at all on CTRL, no bit-bash on STATUS, and no access test anywhere in the block, set at block scope.
// snippet: scratch test body
uvm_resource_db#(bit)::set({"REG::", rm.ctrl.get_full_name()}, "NO_REG_TESTS", 1, this);
uvm_resource_db#(bit)::set({"REG::", rm.status.get_full_name()}, "NO_REG_BIT_BASH_TEST", 1, this);
uvm_resource_db#(bit)::set({"REG::", rm.get_full_name()}, "NO_REG_ACCESS_TEST", 1, this);
seq.model = rm;
seq.tests = UVM_DO_REG_HW_RESET | UVM_DO_REG_BIT_BASH | UVM_DO_REG_ACCESS;
seq.start(null);scratch run, Verilator
UVM_INFO reg/sequences/uvm_reg_hw_reset_seq.svh(153) @ 0: reporter@@seq.reg_hw_reset_seq [uvm_reg_hw_reset_seq] Verifying reset value of register regmodel.len in map "regmodel.map"...
UVM_INFO reg/sequences/uvm_reg_hw_reset_seq.svh(153) @ 75000: reporter@@seq.reg_hw_reset_seq [uvm_reg_hw_reset_seq] Verifying reset value of register regmodel.key in map "regmodel.map"...
UVM_INFO reg/sequences/uvm_reg_mem_built_in_seq.svh(92) @ 115000: reporter@@seq [FINISH_SEQ] Finished reg_hw_reset_seq sequence.
UVM_INFO reg/sequences/uvm_reg_bit_bash_seq.svh(158) @ 115000: reporter@@seq.reg_bit_bash_seq.reg_single_bit_bash_seq [uvm_reg_bit_bash_seq] Verifying bits in register regmodel.len in map "regmodel.map"...
UVM_INFO reg/sequences/uvm_reg_bit_bash_seq.svh(158) @ 5235000: reporter@@seq.reg_bit_bash_seq.reg_single_bit_bash_seq [uvm_reg_bit_bash_seq] Verifying bits in register regmodel.key in map "regmodel.map"...
UVM_INFO reg/sequences/uvm_reg_bit_bash_seq.svh(158) @ 10355000: reporter@@seq.reg_bit_bash_seq.reg_single_bit_bash_seq [uvm_reg_bit_bash_seq] Verifying bits in register regmodel.count in map "regmodel.map"...
UVM_INFO reg/sequences/uvm_reg_mem_built_in_seq.svh(103) @ 12915000: reporter@@seq [FINISH_SEQ] Finished reg_bit_bash_seq sequence.
UVM_ERROR : 0CTRL disappeared from the reset check too: NO_REG_TESTS means every register test. The access sequence never started, because the block-level attribute is checked by the wrapper before it creates the sequence.
Without that block-level attribute, uvm_reg_access_seq runs, and it shows what the bench's backdoor can and cannot do. Its recipe per register is: write ~value through the frontdoor, mirror(UVM_CHECK) through the backdoor, write the old value through the backdoor, then mirror(UVM_CHECK) through the frontdoor. The bench's dma_reg_backdoor reads KEY, LEN and COUNT through a bound interface and refuses writes. Scratch run, Verilator:
UVM_WARNING reg/sequences/uvm_reg_access_seq.svh(291) @ 0: reporter@@seq [uvm_reg_access_seq] Register 'regmodel.ctrl' does not have a backdoor mechanism available
UVM_WARNING reg/sequences/uvm_reg_access_seq.svh(291) @ 0: reporter@@seq [uvm_reg_access_seq] Register 'regmodel.status' does not have a backdoor mechanism available
UVM_INFO reg/sequences/uvm_reg_access_seq.svh(157) @ 0: reporter@@seq.single_reg_access_seq [uvm_reg_access_seq] Verifying access of register 'regmodel.len' in map 'regmodel.map' ...
UVM_WARNING dma_ral_tb.sv(195) @ 76000: reporter [BD_RO] this backdoor is read-only
UVM_WARNING reg/uvm_reg.svh(1664) @ 76000: reporter [PREDICT_NOK] status UVM_NOT_OK; skip prediction.
UVM_ERROR reg/sequences/uvm_reg_access_seq.svh(181) @ 76000: reporter@@seq.single_reg_access_seq [uvm_reg_access_seq] Status was 'UVM_NOT_OK' when writing 'regmodel.len' through backdoor
... the same three lines for regmodel.key ...
UVM_WARNING reg/sequences/uvm_reg_access_seq.svh(140) @ 195000: reporter@@seq.single_reg_access_seq [uvm_reg_access_seq] Register 'regmodel.count' has only RO fields in map regmodel.map, skipping
UVM_ERROR : 2The frontdoor-write, backdoor-read half passed for LEN and KEY; the backdoor-write half failed because the backdoor is read-only by design. The fix is a decision, not a patch: give the backdoor a write path (an HDL path with the simulator's DPI backdoor, or an interface that can deposit), or exclude the block with NO_REG_ACCESS_TEST and say in the testplan that frontdoor/backdoor consistency is covered by the KEY peek() check in dma_ral_job_test. Memories get the same treatment from uvm_mem_access_seq and uvm_mem_walk_seq; the memories lesson runs the walk.
When the bit-bash test was first written, the env had explicit prediction only: the APB monitor feeds uvm_reg_predictor, and auto prediction is off. That is the recommended setup, and it made bit-bash fail almost everywhere. A scratch run of the same test with auto_predict set back to 0 gives this (Verilator):
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 ...
UVM_ERROR : 9494 errors: every bash of LEN's and KEY's field bits except the very first one of each register. The DUT is fine: it yielded exactly the value that was written. The expected value is wrong, and it is always the value from one access earlier. Bit-bash takes its expectation from rg.get() immediately after write() returns. With explicit prediction the mirror is updated when the APB monitor publishes a transfer, a moment after the driver finishes it. So the prediction for the previous read lands after write() has staged the new desired value, and overwrites it with the old one. The first bash of each register passes because nothing was in flight before it.
The bench fixes it by turning auto prediction on for this test (uvm_config_db#(bit)::set(this, "env", "auto_predict", 1)), so the map predicts inside write() before it returns. The explicit predictor stays connected, so traffic from anything else is still seen. The general rule: library sequences that compare against the mirror straight after an access need prediction that has finished by the time the access returns. The same lag, and the same fix, appears in dma_ral_busy_len_test for mirror(UVM_CHECK).
uvm_resource_db#(bit)::set({"REG::", rm.ctrl.get_full_name()}, "NO_REG_TESTS", 0, this) to *re-enable* tests on CTRL. What happens?CTRL stays excluded. The sequences only ask whether a resource with that name exists (get_by_name(...) != null); they never read its value. To re-enable the tests, do not set the attribute at all in that test.
No. COUNT's field is volatile, so its compare is UVM_NO_CHECK and uvm_reg_hw_reset_seq skips the register without a message, as the log shows (only CTRL, LEN and KEY are verified). Cover the row with a directed read() right after reset and an explicit compare against 0.
Bits the model does not describe are bashed as read-only: bit-bash writes each one flipped and expects it to read back as the mirror value, which is 0. It is checking that the unused upper bits are tied to zero. That is useful, but if the spec allows them to read as anything, model them as a reserved field with UVM_NO_CHECK.
Next: the sequences check that registers exist and behave as described. They say nothing about which values the tests used. Register Coverage asks what register coverage measures, and why it is so often switched off without anybody noticing.