Using the integrated register model — the desired/mirrored value model, all access methods (write, read, set, get, update, mirror, peek, poke, reset), complete sequence patterns, and the full suite of UVM built-in register and memory test sequences.
Every register in the model maintains two internal values that track the relationship between the software model and the actual DUT hardware:
set() or randomize(). Written to hardware with update().Both start equal to the register’s reset value. They diverge when a set() call changes the desired without writing to hardware, or when hardware modifies a volatile field without a software write.
The most commonly used methods. Both trigger actual bus transfers (front door) or zero-time database access (back door). Both update the mirror via the predictor on completion.
// ── write() — performs a bus write to the register ─────── // task write(output uvm_status_e status, // input uvm_reg_data_t value, // input uvm_path_e path = UVM_DEFAULT_PATH, // input uvm_reg_map map = null, // input uvm_sequence_base parent = null, ...) gpio_reg_block rm; uvm_status_e status; uvm_reg_data_t data; // Write the direction register — all 32 GPIOs as outputs rm.dir_reg.write(status, 32'hFFFF_FFFF, .parent(this)); if (status != UVM_IS_OK) `uvm_error("SEQ", "dir_reg write failed") // ── read() — performs a bus read from the register ─────── // task read(output uvm_status_e status, // output uvm_reg_data_t value, ...) rm.data_reg.read(status, data, .parent(this)); `uvm_info("SEQ", $sformatf("GPIO data = 0x%08h", data), UVM_MEDIUM) // ── Field-level access (use with caution) ──────────────── // Field-level write only works reliably when the field occupies // exactly one byte lane. For portability, prefer register-level access. rm.ien_reg.ien.write(status, 32'h0000_000F, .parent(this)); // ── Back door access ────────────────────────────────────── rm.data_reg.write(status, 32'hDEAD_BEEF, .path(UVM_BACKDOOR)); // zero simulation time rm.data_reg.read(status, data, .path(UVM_BACKDOOR)); // reads RTL directly
set() and get() are purely internal register model operations — they modify or read the desired value without touching the hardware. No bus cycle occurs. They are used to build up a new register value field by field before committing it with update().
// ── set() assigns the desired value (no bus cycle) ─────── // function void set(uvm_reg_data_t value, ...) // Register-level set: set the desired value for the whole register rm.ctrl_reg.set(32'h0000_0000); // Field-level set: build up value field by field rm.ctrl_reg.go_bsy.set(1); // set go bit rm.ctrl_reg.ie.set(0); // disable interrupt rm.ctrl_reg.char_len.set(7'd8); // 8-bit character // Now write the assembled value to hardware: rm.ctrl_reg.update(status, .parent(this)); // ── get() returns the desired value (no bus cycle) ─────── // function uvm_reg_data_t get(...) data = rm.ctrl_reg.get(); // register level logic [6:0] len = rm.ctrl_reg.char_len.get(); // field level // ── get_mirrored_value() — returns the mirror, not desired data = rm.ctrl_reg.get_mirrored_value();
Key behaviour of set() depends on the field access type. For RW fields, set() stores the value as-is. For W1C fields, set() computes the bitwise result — writing 1 clears bits in the mirror. For RO fields, set() has no effect. For cumulative sets before an update(), each set uses the previous desired as the new mirror input.
update() compares the desired value against the mirrored value. If they differ, it performs a bus write to bring the hardware in line with the desired. If they are already equal, update() produces no bus activity.
update() can be called at register level (one write) or block level (iterates through all registers that need updating).
// ── Register-level update ───────────────────────────────── // task update(output uvm_status_e status, // input uvm_path_e path = UVM_DEFAULT_PATH, // input uvm_sequence_base parent = null, ...) // Configure control register fields, then write once rm.ctrl_reg.char_len.set(7'd8); rm.ctrl_reg.ie.set(0); rm.ctrl_reg.go_bsy.set(0); rm.ctrl_reg.update(status, .parent(this)); // one bus write // ── Block-level update: write ALL changed registers ────── // Iterates registers in block order — fixed order only rm.update(status, .parent(this)); // ── Randomise then update: auto-configure the DUT ──────── if (!rm.ctrl_reg.randomize() with { char_len.value inside {[31:33]}; // 32-bit characters go_bsy.value == 0; // don't start yet }) `uvm_fatal("SEQ", "ctrl_reg randomize failed") rm.ctrl_reg.update(status, .parent(this));
mirror() reads the hardware register and updates the model’s mirrored value — it is the inverse of update(). Unlike read(), mirror() does not return the data value to the caller. Instead it optionally checks the read-back value against the existing mirrored value and reports an error if they differ.
// ── mirror() — read hardware, update mirror ─────────────── // task mirror(output uvm_status_e status, // input uvm_check_e check = UVM_NO_CHECK, // input uvm_path_e path = UVM_DEFAULT_PATH, ...) // Mirror without checking — just update the local mirror rm.data_reg.mirror(status, .parent(this)); // Mirror WITH checking — reports error if HW != expected mirror rm.dir_reg.mirror(status, UVM_CHECK, .parent(this)); if (status != UVM_IS_OK) `uvm_error("SEQ", "dir_reg mirror check failed!") // Block-level mirror — reads ALL registers in the block // Generates one read per register — can be slow for large blocks rm.mirror(status, UVM_CHECK, .parent(this)); // Backdoor mirror — zero simulation time rm.mirror(status, UVM_CHECK, .path(UVM_BACKDOOR));
peek() and poke() are the backdoor shorthand equivalents of read() and write(). They bypass the bus entirely and directly read or force the RTL register flip-flops using the simulator database. Both update the model automatically and complete in zero simulation time.
Backdoor access requires HDL paths to be defined on the registers during model construction (the third argument of reg.configure()).
// ── poke() — force RTL bits, zero time ─────────────────── rm.data_reg.poke(status, 32'hCAFE_BABE, .parent(this)); // ── peek() — read RTL bits, zero time ──────────────────── rm.data_reg.peek(status, data, .parent(this)); // Field-level peek/poke are also supported: rm.ctrl_reg.char_len.peek(status, data, .parent(this)); rm.ctrl_reg.char_len.poke(status, 7'd16, .parent(this)); // ── Typical use: initialise registers before test starts ─ function void start_of_simulation_phase(uvm_phase phase); uvm_status_e s; // Set known state without driving bus cycles m_rm.data_reg.poke(s, 32'h0000_0000); m_rm.dir_reg.poke(s, 32'hFFFF_0000); m_rm.ien_reg.poke(s, 32'h0000_000F); endfunction
reset() sets the register model’s desired and mirrored values back to the configured reset value. It produces no bus cycles — it only resets the software model. Call it whenever the DUT receives a hardware reset to keep the model in sync.
get_reset() returns the configured reset value without modifying anything. Use it in reset-value verification sequences.
// ── reset() — reset the software model to reset values ─── // function void reset(string kind = "HARD") rm.reset(); // block-level — reset all registers rm.ctrl_reg.reset(); // register-level rm.ctrl_reg.char_len.reset(); // field-level // ── Typical usage: align model to DUT hardware reset ───── task run_phase(uvm_phase phase); // At start, or after PRESETn deasserts @(posedge vif.PRESETn); m_rm.reset(); // sync software model to hardware reset endtask // ── get_reset() — read expected reset value ─────────────── // function uvm_reg_data_t get_reset(string kind = "HARD") uvm_reg_data_t exp_val = rm.ctrl_reg.get_reset(); rm.ctrl_reg.read(status, data, .parent(this)); if (data != exp_val) `uvm_error("SEQ", $sformatf( "ctrl_reg reset mismatch: exp=0x%0h act=0x%0h", exp_val, data))
| Method | Bus cycle? | Time | Updates desired | Updates mirror | Typical use |
|---|---|---|---|---|---|
write() | Yes (FD) / No (BD) | Real / Zero | Yes | Via predictor (FD) / Auto (BD) | Write a known value to hardware |
read() | Yes (FD) / No (BD) | Real / Zero | No | Via predictor (FD) / Auto (BD) | Read and return current hardware value |
set() | No | Zero | Yes | No | Stage field values before update() |
get() | No | Zero | No | No | Read current desired value |
get_mirrored_value() | No | Zero | No | No | Read last known hardware state |
update() | Yes if desired≠mirror | Real (if write needed) | No | Via predictor | Commit staged field values to hardware |
mirror() | Yes (FD) / No (BD) | Real / Zero | No | Yes | Refresh mirror; optionally verify hardware matches model |
poke() | No (backdoor only) | Zero | Yes | Yes | Force register bits directly (initialisation, debug) |
peek() | No (backdoor only) | Zero | No | Yes | Read register bits directly (fast check without bus) |
reset() | No | Zero | Reset value | Reset value | Sync model after hardware reset |
get_reset() | No | Zero | No | No | Get expected reset value for comparison |
A base sequence class retrieves the register model handle once and makes it available to derived sequences:
class gpio_base_reg_seq extends uvm_sequence #(uvm_sequence_item); `uvm_object_utils(gpio_base_reg_seq) gpio_reg_block rm; uvm_status_e status; uvm_reg_data_t data; function new(string name = "gpio_base_reg_seq"); super.new(name); endfunction virtual task body(); if (!uvm_config_db #(gpio_reg_block)::get( m_sequencer, "", "gpio_rm", rm)) `uvm_fatal("SEQ", "No register model handle!") endtask endclass // ── Pattern 1: write specific register values ───────────── class gpio_init_seq extends gpio_base_reg_seq; `uvm_object_utils(gpio_init_seq) function new(string n="gpio_init_seq"); super.new(n); endfunction task body(); super.body(); rm.dir_reg.write(status, 32'hFFFF_0000, .parent(this)); rm.ien_reg.write(status, 32'h0000_000F, .parent(this)); rm.data_reg.write(status, 32'h0000_0000, .parent(this)); endtask endclass // ── Pattern 2: set fields then update ───────────────────── class gpio_cfg_seq extends gpio_base_reg_seq; `uvm_object_utils(gpio_cfg_seq) function new(string n="gpio_cfg_seq"); super.new(n); endfunction rand logic [31:0] output_mask; task body(); super.body(); // Stage field values (no bus cycles) rm.dir_reg.gpio_dir.set(output_mask); rm.ien_reg.ien.set(32'h0); // Write them all at once rm.dir_reg.update(status, .parent(this)); rm.ien_reg.update(status, .parent(this)); endtask endclass // ── Pattern 3: read-back and verify ─────────────────────── class gpio_verify_seq extends gpio_base_reg_seq; `uvm_object_utils(gpio_verify_seq) function new(string n="gpio_verify_seq"); super.new(n); endfunction task body(); super.body(); // Mirror all registers (read HW, update model) rm.mirror(status, UVM_CHECK, .parent(this)); if (status != UVM_IS_OK) `uvm_error("SEQ", "Register mirror check FAILED") endtask endclass
UVM provides a library of register test sequences that can be applied to any register model without writing a single line of test code. They cover the most important sanity checks for register data paths.
| Sequence | Level | What it tests | Disable attribute |
|---|---|---|---|
uvm_reg_hw_reset_seq | Block or Reg | Reads every register and checks that the hardware reset value matches the value specified in the model | NO_REG_HW_RESET_TEST |
uvm_reg_single_bit_bash_seq | Reg only | Writes then reads walking 1s and 0s to all RW bits in the register, verifying data path integrity | NO_REG_BIT_BASH_TEST |
uvm_reg_bit_bash_seq | Block only | Executes uvm_reg_single_bit_bash_seq on every register in the block and sub-blocks | NO_REG_BIT_BASH_TEST |
uvm_reg_single_access_seq | Reg only | Writes via front door, verifies via back door; writes via back door, reads back via front door. Tests both paths. Requires HDL path. | NO_REG_ACCESS_TEST |
uvm_reg_access_seq | Block only | Runs uvm_reg_single_access_seq on every register in the block | NO_REG_ACCESS_TEST |
uvm_reg_shared_access_seq | Reg only | For registers in multiple maps: writes in one map, reads back from all other maps. Tests address aliasing. | NO_SHARED_ACCESS_TEST |
| Sequence | Level | What it tests | Disable attribute |
|---|---|---|---|
uvm_mem_single_walk_seq | Mem only | Writes a walking-ones pattern to every location in the memory range, then reads back to verify | NO_MEM_WALK_TEST |
uvm_mem_walk_seq | Block only | Runs uvm_mem_single_walk_seq on all memories in the block | NO_MEM_WALK_TEST |
uvm_mem_single_access_seq | Mem only | For each memory location: writes via front door, checks via back door; writes via back door, reads via front door. Requires HDL path. | NO_MEM_ACCESS_TEST |
uvm_mem_access_seq | Block only | Runs uvm_mem_single_access_seq on all memories in the block | NO_MEM_ACCESS_TEST |
uvm_reg_mem_built_in_seq | Block only | Runs ALL block-level built-in register and memory sequences — a complete basic sanity suite | All of the above |
// ── Run the hardware reset check on the whole block ─────── task run_phase(uvm_phase phase); uvm_reg_hw_reset_seq reset_seq; phase.raise_objection(this); reset_seq = uvm_reg_hw_reset_seq::type_id::create("reset_seq"); reset_seq.model = m_env_cfg.gpio_rm; // point at the register block reset_seq.start(m_env.m_agent.m_seqr); phase.drop_objection(this); endtask // ── Run all built-in sequences at once ──────────────────── task run_phase(uvm_phase phase); uvm_reg_mem_built_in_seq all_seq; phase.raise_objection(this); all_seq = uvm_reg_mem_built_in_seq::type_id::create("all_seq"); all_seq.model = m_env_cfg.gpio_rm; all_seq.start(m_env.m_agent.m_seqr); phase.drop_objection(this); endtask // ── Exclude specific registers from a built-in test ─────── // Done in the register block build() or from a sequence // using UVM resource_db: uvm_resource_db #(bit)::set( {"REG::", gpio_rm.isr_reg.get_full_name()}, "NO_REG_HW_RESET_TEST", 1); // The isr_reg is a W1C register — reset test not applicable
NO_REG_HW_RESET_TEST and similar attributes to exclude these registers — set in the block build() for permanent exclusion, or from a sequence for test-specific exclusion.| Method | Key fact |
|---|---|
write(status, value, .parent(this)) | Front-door write — bus cycle, updates mirror via predictor |
read(status, value, .parent(this)) | Front-door read — bus cycle, returns data in value, updates mirror |
set(value) | No bus cycle. Sets desired value only. Respects access type semantics. |
get() | No bus cycle. Returns desired value. |
get_mirrored_value() | No bus cycle. Returns last known hardware value (mirror). |
update(status, .parent(this)) | Bus write only if desired≠mirror. Commits staged values to hardware. |
mirror(status, check, .parent(this)) | Bus read, updates mirror. UVM_CHECK reports error if value differs. |
poke(status, value) | Backdoor write — zero time, forces RTL. Updates desired and mirror. |
peek(status, value) | Backdoor read — zero time. Updates mirror. |
reset() | No bus cycle. Sets desired and mirror to configured reset value. Call on hardware reset. |
get_reset() | Returns configured reset value for comparison. No bus cycle. |
| set/update pattern | set() fields individually → update() writes once. Preferred for multi-field configuration. |
| uvm_reg_hw_reset_seq | Checks all registers read as their reset value |
| uvm_reg_bit_bash_seq | Walking 1s/0s on all RW bits — verifies data path |
| uvm_reg_mem_built_in_seq | Runs all block-level built-in sequences in one call |
| Disable attribute | Set via uvm_resource_db with “REG::” + reg.get_full_name() and attribute string |