This UVM register model example puts every RAL piece together for one small design. It shows how a register model is written for the design, how it is placed in a register environment with an adapter and a predictor, and how a test uses it to write, read, check and update the design registers.
10 min read | Intermediate Level
What You'll Learn
- Write a register model for a design with control, timer and status registers
- Build a register environment with an adapter and a predictor
- Connect the register model to an APB agent in the top environment
- Use
write(),read(),set(),predict(),mirror()andupdate()in a test - Read the log and waveform to see what each register call does on the bus
Design
The design represents a traffic light controller that is configured by writing into its registers through an APB interface.
- The ctl register has fields to enable the module (
mod_en), blink yellow (bl_yellow), blink red (bl_red) and select a timingprofile. - The two timer registers store the time between transitions from one light to the next. The
profilebit chooses between the two timers, which is useful for peak and off-peak hours. - The stat register is read-only and returns the current state of the design: yellow, red or green.
This is not a complete design, because the goal is only to show how its registers are accessed through a UVM register model. All module ports belong to the APB protocol.
module traffic ( input pclk,
input presetn,
input [31:0] paddr,
input [31:0] pwdata,
input psel,
input pwrite,
input penable,
// Outputs
output [31:0] prdata);
reg [3:0] ctl_reg; // profile, blink_red, blink_yellow, mod_en RW
reg [1:0] stat_reg; // state[1:0]
reg [31:0] timer_0; // timer_g2y[31:20], timer_r2g[19:8], timer_y2r[7:0] RW
reg [31:0] timer_1; // timer_g2y[31:20], timer_r2g[19:8], timer_y2r[7:0] RW
reg [31:0] data_in;
reg [31:0] rdata_tmp;
// Set all registers to default values
always @ (posedge pclk) begin
if (!presetn) begin
data_in <= 0;
ctl_reg <= 0;
stat_reg <= 0;
timer_0 <= 32'hcafe_1234;
timer_1 <= 32'hface_5678;
end
end
// Capture write data
always @ (posedge pclk) begin
if (presetn & psel & penable)
if (pwrite)
case (paddr)
'h0 : ctl_reg <= pwdata;
'h4 : timer_0 <= pwdata;
'h8 : timer_1 <= pwdata;
'hc : stat_reg <= pwdata;
endcase
end
// Provide read data
always @ (penable) begin
if (psel & !pwrite)
case (paddr)
'h0 : rdata_tmp <= ctl_reg;
'h4 : rdata_tmp <= timer_0;
'h8 : rdata_tmp <= timer_1;
'hc : rdata_tmp <= stat_reg;
endcase
end
assign prdata = (psel & penable & !pwrite) ? rdata_tmp : 'hz;
endmodule
The RTL accepts a write to address 'hc and updates stat_reg, even though the register is described as read-only. The test below writes to this address on purpose, to show how the register model handles a read-only register.
Interface
The interface holds the APB signals. It is passed to the testbench as a virtual interface, so that the driver can drive values into the design. Clocking blocks and modports are left out to keep things simple, although they are recommended in a real project.
interface bus_if (input pclk);
logic [31:0] paddr;
logic [31:0] pwdata;
logic [31:0] prdata;
logic pwrite;
logic psel;
logic penable;
logic presetn;
endinterface
Register Model Example
The register model below describes the design registers. In real projects, a script usually generates it from a register specification such as IP-XACT. The model is built in three layers:
- Registers: ral_cfg_ctl, ral_cfg_stat and ral_cfg_timer extend
uvm_regand configure their fields inbuild(). - Register block: ral_block_traffic_cfg creates the registers and adds them to its address map at offsets
0x0,0x4,0x8and0xc. - Top block: ral_sys_traffic contains the cfg block and adds its map as a submap at offset 0.
// Register definition for the register called "ctl"
class ral_cfg_ctl extends uvm_reg;
rand uvm_reg_field mod_en; // Enables the module
rand uvm_reg_field bl_yellow; // Blinks yellow
rand uvm_reg_field bl_red; // Blinks red
rand uvm_reg_field profile; // 1 : Peak, 0 : Off-Peak
`uvm_object_utils(ral_cfg_ctl)
function new(string name = "traffic_cfg_ctrl");
super.new(name, 32, build_coverage(UVM_NO_COVERAGE));
endfunction: new
// Build all register field objects
virtual function void build();
this.mod_en = uvm_reg_field::type_id::create("mod_en",, get_full_name());
this.bl_yellow = uvm_reg_field::type_id::create("bl_yellow",,get_full_name());
this.bl_red = uvm_reg_field::type_id::create("bl_red",, get_full_name());
this.profile = uvm_reg_field::type_id::create("profile",, get_full_name());
// configure(parent, size, lsb_pos, access, volatile, reset, has_reset, is_rand, individually_accessible);
this.mod_en.configure(this, 1, 0, "RW", 0, 1'h0, 1, 0, 0);
this.bl_yellow.configure(this, 1, 1, "RW", 0, 1'h0, 1, 0, 0);
this.bl_red.configure(this, 1, 2, "RW", 0, 1'h0, 1, 0, 0);
this.profile.configure(this, 1, 3, "RW", 0, 1'h0, 1, 0, 0);
endfunction
endclass
// Register definition for the register called "stat"
class ral_cfg_stat extends uvm_reg;
uvm_reg_field state; // Current state of the design
`uvm_object_utils(ral_cfg_stat)
function new(string name = "ral_cfg_stat");
super.new(name, 32, build_coverage(UVM_NO_COVERAGE));
endfunction
virtual function void build();
this.state = uvm_reg_field::type_id::create("state",, get_full_name());
// configure(parent, size, lsb_pos, access, volatile, reset, has_reset, is_rand, individually_accessible);
this.state.configure(this, 2, 0, "RO", 0, 1'h0, 0, 0, 0);
endfunction
endclass
// Register definition for the register called "timer"
class ral_cfg_timer extends uvm_reg;
uvm_reg_field timer; // Time for which it blinks
`uvm_object_utils(ral_cfg_timer)
function new(string name = "traffic_cfg_timer");
super.new(name, 32,build_coverage(UVM_NO_COVERAGE));
endfunction
virtual function void build();
this.timer = uvm_reg_field::type_id::create("timer",,get_full_name());
// configure(parent, size, lsb_pos, access, volatile, reset, has_reset, is_rand, individually_accessible);
this.timer.configure(this, 32, 0, "RW", 0, 32'hCAFE1234, 1, 0, 1);
this.timer.set_reset('h0, "SOFT");
endfunction
endclass
// These registers are grouped together to form a register block called "cfg"
class ral_block_traffic_cfg extends uvm_reg_block;
rand ral_cfg_ctl ctrl; // RW
rand ral_cfg_timer timer[2]; // RW
ral_cfg_stat stat; // RO
`uvm_object_utils(ral_block_traffic_cfg)
function new(string name = "traffic_cfg");
super.new(name, build_coverage(UVM_NO_COVERAGE));
endfunction
virtual function void build();
this.default_map = create_map("", 0, 4, UVM_LITTLE_ENDIAN, 0);
this.ctrl = ral_cfg_ctl::type_id::create("ctrl",,get_full_name());
this.ctrl.configure(this, null, "");
this.ctrl.build();
this.default_map.add_reg(this.ctrl, `UVM_REG_ADDR_WIDTH'h0, "RW", 0);
this.timer[0] = ral_cfg_timer::type_id::create("timer[0]",,get_full_name());
this.timer[0].configure(this, null, "");
this.timer[0].build();
this.default_map.add_reg(this.timer[0], `UVM_REG_ADDR_WIDTH'h4, "RW", 0);
this.timer[1] = ral_cfg_timer::type_id::create("timer[1]",,get_full_name());
this.timer[1].configure(this, null, "");
this.timer[1].build();
this.default_map.add_reg(this.timer[1], `UVM_REG_ADDR_WIDTH'h8, "RW", 0);
this.stat = ral_cfg_stat::type_id::create("stat",,get_full_name());
this.stat.configure(this, null, "");
this.stat.build();
this.default_map.add_reg(this.stat, `UVM_REG_ADDR_WIDTH'hc, "RO", 0);
endfunction
endclass
// The register block is placed in the top level model class definition
class ral_sys_traffic extends uvm_reg_block;
rand ral_block_traffic_cfg cfg;
`uvm_object_utils(ral_sys_traffic)
function new(string name = "traffic");
super.new(name);
endfunction
function void build();
this.default_map = create_map("", 0, 4, UVM_LITTLE_ENDIAN, 0);
this.cfg = ral_block_traffic_cfg::type_id::create("cfg",,get_full_name());
this.cfg.configure(this, "tb_top.pB0");
this.cfg.build();
this.default_map.add_submap(this.cfg.default_map, `UVM_REG_ADDR_WIDTH'h0);
endfunction
endclass
Each field is set up with configure(), whose arguments are listed in the comment above each call. For example, bl_yellow is a 1-bit field at bit position 1 with "RW" access and a reset value of 0. The timer is modeled as a single 32-bit field,.
Register Environment
The register environment puts the register model, the adapter and the predictor in one place. It can then be plugged into any testbench, and the top environment does not need to know how these parts are connected.
The adapter depends on the bus protocol, so a custom class reg2apb_adapter is derived from uvm_reg_adapter. Its two functions convert between the two worlds:
reg2bus()turns a register operation into an APB packet bus_pkt.bus2reg()turns an observed APB packet back into a register operation.
class reg2apb_adapter extends uvm_reg_adapter;
`uvm_object_utils (reg2apb_adapter)
function new (string name = "reg2apb_adapter");
super.new (name);
endfunction
virtual function uvm_sequence_item reg2bus (const ref uvm_reg_bus_op rw);
bus_pkt pkt = bus_pkt::type_id::create ("pkt");
pkt.write = (rw.kind == UVM_WRITE) ? 1: 0;
pkt.addr = rw.addr;
pkt.data = rw.data;
`uvm_info ("adapter", $sformatf ("reg2bus addr=0x%0h data=0x%0h kind=%s", pkt.addr, pkt.data, rw.kind.name()), UVM_DEBUG)
return pkt;
endfunction
virtual function void bus2reg (uvm_sequence_item bus_item, ref uvm_reg_bus_op rw);
bus_pkt pkt;
if (! $cast (pkt, bus_item)) begin
`uvm_fatal ("reg2apb_adapter", "Failed to cast bus_item to pkt")
end
rw.kind = pkt.write ? UVM_WRITE : UVM_READ;
rw.addr = pkt.addr;
rw.data = pkt.data;
`uvm_info ("adapter", $sformatf("bus2reg : addr=0x%0h data=0x%0h kind=%s status=%s", rw.addr, rw.data, rw.kind.name(), rw.status.name()), UVM_DEBUG)
endfunction
endclass
// Register environment class puts together the model, adapter and the predictor
class reg_env extends uvm_env;
`uvm_component_utils (reg_env)
function new (string name="reg_env", uvm_component parent);
super.new (name, parent);
endfunction
ral_sys_traffic m_ral_model; // Register Model
reg2apb_adapter m_reg2apb; // Convert Reg Tx <-> Bus-type packets
uvm_reg_predictor #(bus_pkt) m_apb2reg_predictor; // Map APB tx to register in model
my_agent m_agent; // Agent to drive/monitor transactions
virtual function void build_phase (uvm_phase phase);
super.build_phase (phase);
m_ral_model = ral_sys_traffic::type_id::create ("m_ral_model", this);
m_reg2apb = reg2apb_adapter :: type_id :: create ("m_reg2apb");
m_apb2reg_predictor = uvm_reg_predictor #(bus_pkt) :: type_id :: create ("m_apb2reg_predictor", this);
m_ral_model.build ();
m_ral_model.lock_model ();
uvm_config_db #(ral_sys_traffic)::set (null, "uvm_test_top", "m_ral_model", m_ral_model);
endfunction
virtual function void connect_phase (uvm_phase phase);
super.connect_phase (phase);
m_apb2reg_predictor.map = m_ral_model.default_map;
m_apb2reg_predictor.adapter = m_reg2apb;
endfunction
endclass
The predictor m_apb2reg_predictor needs the address map and the adapter. Its bus_in port is connected to the APB monitor in the top environment. Every observed APB transfer updates the mirrored value of the matching register.
APB Agent Example
AMBA APB is a bus protocol for low-bandwidth peripheral accesses, and it is often used to access control registers. The agent below is a simple model of an APB agent, enough for this example. It does not handle many features of the actual APB protocol.
// Declare a sequence_item for the APB transaction
class bus_pkt extends uvm_sequence_item;
rand bit [31:0] addr;
rand bit [31:0] data;
rand bit write;
`uvm_object_utils_begin (bus_pkt)
`uvm_field_int (addr, UVM_ALL_ON)
`uvm_field_int (data, UVM_ALL_ON)
`uvm_field_int (write, UVM_ALL_ON)
`uvm_object_utils_end
function new (string name = "bus_pkt");
super.new (name);
endfunction
constraint c_addr { addr inside {0, 4, 8};}
endclass
// Drives a given apb transaction packet to the APB interface
class my_driver extends uvm_driver #(bus_pkt);
`uvm_component_utils (my_driver)
bus_pkt pkt;
virtual bus_if vif;
function new (string name = "my_driver", uvm_component parent);
super.new (name, parent);
endfunction
virtual function void build_phase (uvm_phase phase);
super.build_phase (phase);
if (! uvm_config_db#(virtual bus_if)::get (this, "*", "bus_if", vif))
`uvm_error ("DRVR", "Did not get bus if handle")
endfunction
virtual task run_phase (uvm_phase phase);
bit [31:0] data;
vif.psel <= 0;
vif.penable <= 0;
vif.pwrite <= 0;
vif.paddr <= 0;
vif.pwdata <= 0;
forever begin
seq_item_port.get_next_item (pkt);
if (pkt.write)
write (pkt.addr, pkt.data);
else begin
read (pkt.addr, data);
pkt.data = data;
end
seq_item_port.item_done ();
end
endtask
virtual task read ( input bit [31:0] addr,
output logic [31:0] data);
vif.paddr <= addr;
vif.pwrite <= 0;
vif.psel <= 1;
@(posedge vif.pclk);
vif.penable <= 1;
@(posedge vif.pclk);
data = vif.prdata;
vif.psel <= 0;
vif.penable <= 0;
endtask
virtual task write ( input bit [31:0] addr,
input bit [31:0] data);
vif.paddr <= addr;
vif.pwdata <= data;
vif.pwrite <= 1;
vif.psel <= 1;
@(posedge vif.pclk);
vif.penable <= 1;
@(posedge vif.pclk);
vif.psel <= 0;
vif.penable <= 0;
endtask
endclass
// Monitors the APB interface for any activity and reports out
// through an analysis port
class my_monitor extends uvm_monitor;
`uvm_component_utils (my_monitor)
function new (string name="my_monitor", uvm_component parent);
super.new (name, parent);
endfunction
uvm_analysis_port #(bus_pkt) mon_ap;
virtual bus_if vif;
virtual function void build_phase (uvm_phase phase);
super.build_phase (phase);
mon_ap = new ("mon_ap", this);
uvm_config_db #(virtual bus_if)::get (null, "uvm_test_top.*", "bus_if", vif);
endfunction
virtual task run_phase (uvm_phase phase);
fork
forever begin
@(posedge vif.pclk);
if (vif.psel & vif.penable & vif.presetn) begin
bus_pkt pkt = bus_pkt::type_id::create ("pkt");
pkt.addr = vif.paddr;
if (vif.pwrite)
pkt.data = vif.pwdata;
else
pkt.data = vif.prdata;
pkt.write = vif.pwrite;
mon_ap.write (pkt);
end
end
join_none
endtask
endclass
// The agent puts together the driver, sequencer and monitor
class my_agent extends uvm_agent;
`uvm_component_utils (my_agent)
function new (string name="my_agent", uvm_component parent);
super.new (name, parent);
endfunction
my_driver m_drvr;
my_monitor m_mon;
uvm_sequencer #(bus_pkt) m_seqr;
virtual function void build_phase (uvm_phase phase);
super.build_phase (phase);
m_drvr = my_driver::type_id::create ("m_drvr", this);
m_seqr = uvm_sequencer#(bus_pkt)::type_id::create ("m_seqr", this);
m_mon = my_monitor::type_id::create ("m_mon", this);
endfunction
virtual function void connect_phase (uvm_phase phase);
super.connect_phase (phase);
m_drvr.seq_item_port.connect (m_seqr.seq_item_export);
endfunction
endclass
Testbench Environment
The top environment creates the APB agent and the register environment, and connects them in connect_phase():
- The monitor's analysis port is connected to the predictor's
bus_inport. set_sequencer()tells the default map which sequencer and adapter to use for front-door accesses.
class my_env extends uvm_env;
`uvm_component_utils (my_env)
my_agent m_agent;
reg_env m_reg_env;
function new (string name = "my_env", uvm_component parent);
super.new (name, parent);
endfunction
virtual function void build_phase (uvm_phase phase);
super.build_phase (phase);
m_agent = my_agent::type_id::create ("m_agent", this);
m_reg_env = reg_env::type_id::create ("m_reg_env", this);
uvm_reg::include_coverage ("*", UVM_CVR_ALL);
endfunction
// Connect analysis port of monitor with predictor, assign agent to register env
// and set default map of the register env
virtual function void connect_phase (uvm_phase phase);
super.connect_phase (phase);
m_reg_env.m_agent = m_agent;
m_agent.m_mon.mon_ap.connect (m_reg_env.m_apb2reg_predictor.bus_in);
m_reg_env.m_ral_model.default_map.set_sequencer(m_agent.m_seqr, m_reg_env.m_reg2apb);
endfunction
endclass
Sequences
This sequence brings the DUT out of reset. It gets the virtual interface handle from uvm_config_db and drives the reset input directly. Register accesses can also be written inside a sequence like this, but here they are called from the test class to keep the example short.
class reset_seq extends uvm_sequence;
`uvm_object_utils (reset_seq)
function new (string name = "reset_seq");
super.new (name);
endfunction
virtual bus_if vif;
task body ();
if (!uvm_config_db #(virtual bus_if) :: get (null, "uvm_test_top.*", "bus_if", vif))
`uvm_fatal ("VIF", "No vif")
`uvm_info ("RESET", "Running reset ...", UVM_MEDIUM);
vif.presetn <= 0;
@(posedge vif.pclk) vif.presetn <= 1;
@ (posedge vif.pclk);
endtask
endclass
Test
A base test builds the environment and applies reset in reset_phase(). Every other test extends it and only adds its own stimulus. The test reg_rw_test runs in main_phase(), which starts after the DUT is out of reset.
class base_test extends uvm_test;
`uvm_component_utils (base_test)
my_env m_env;
reset_seq m_reset_seq;
uvm_status_e status;
function new (string name = "base_test", uvm_component parent);
super.new (name, parent);
endfunction
// Build the testbench environment, and reset sequence
virtual function void build_phase (uvm_phase phase);
super.build_phase (phase);
m_env = my_env::type_id::create ("m_env", this);
m_reset_seq = reset_seq::type_id::create ("m_reset_seq", this);
endfunction
// In the reset phase, apply reset
virtual task reset_phase (uvm_phase phase);
super.reset_phase (phase);
phase.raise_objection (this);
m_reset_seq.start (m_env.m_agent.m_seqr);
phase.drop_objection (this);
endtask
endclass
class reg_rw_test extends base_test;
`uvm_component_utils (reg_rw_test)
function new (string name="reg_rw_test", uvm_component parent);
super.new (name, parent);
endfunction
// Note that main_phase comes after reset_phase, and is performed when
// DUT is out of reset. "reset_phase" is already defined in base_test
// and is always called when this test is started
virtual task main_phase(uvm_phase phase);
ral_sys_traffic m_ral_model;
uvm_status_e status;
int rdata;
phase.raise_objection(this);
m_env.m_reg_env.set_report_verbosity_level (UVM_HIGH);
// Get register model from config_db
uvm_config_db#(ral_sys_traffic)::get(null, "uvm_test_top", "m_ral_model", m_ral_model);
// Write 0xcafe_feed to the timer[1] register, and read it back
m_ral_model.cfg.timer[1].write (status, 32'hcafe_feed);
m_ral_model.cfg.timer[1].read (status, rdata);
// Set 0xface as the desired value for timer[1] register
m_ral_model.cfg.timer[1].set(32'hface);
`uvm_info(get_type_name(), $sformatf("desired=0x%0h mirrored=0x%0h", m_ral_model.cfg.timer[1].get(), m_ral_model.cfg.timer[1].get_mirrored_value()), UVM_MEDIUM)
// Predict that current value of timer[1] is 0xcafe_feed and check it is true
m_ral_model.cfg.timer[1].predict(32'hcafe_feed);
m_ral_model.cfg.timer[1].mirror(status, UVM_CHECK);
// Set desired value of the field "bl_yellow" in register ctrl to 1
// Then start bus transactions by calling "update" to update DUT with
// desired value
m_ral_model.cfg.ctrl.bl_yellow.set(1);
m_ral_model.cfg.update(status);
// Attempt to write into a RO register "stat" with some value
m_ral_model.cfg.stat.write(status, 32'h12345678);
phase.drop_objection(this);
endtask
// Before end of simulation, allow some time for unfinished transactions to
// be over
virtual task shutdown_phase(uvm_phase phase);
super.shutdown_phase(phase);
phase.raise_objection(this);
#100ns;
phase.drop_objection(this);
endtask
endclass
ncsim> run ---------------------------------------------------------------- CDNS-UVM-1.1d (15.20-s044) (C) 2007-2013 Mentor Graphics Corporation (C) 2007-2013 Cadence Design Systems, Inc. (C) 2006-2013 Synopsys, Inc. (C) 2011-2013 Cypress Semiconductor Corp. ---------------------------------------------------------------- UVM_INFO @ 0: reporter [RNTST] Running test reg_rw_test... UVM_INFO ./tb/my_env.sv(215) @ 0: uvm_test_top.m_env.m_agent.m_seqr@@m_reset_seq [RESET] Running reset ... UVM_INFO ./tb/test_lib.sv(80) @ 110: uvm_test_top [reg_rw_test] desired=0xface mirrored=0xcafefeed --- UVM Report catcher Summary ---

The log and the waveform show what each register call does:
- write() and read() (30 to 110):
32'hcafe_feedis written to timer[1] at address 8, and read back throughprdata. - set() at 110: only the desired value changes, so the log shows
desired=0xfacewhilemirrored=0xcafefeed. Nothing happens on the bus. - predict() and mirror() (110 to 150):
predict()sets the model value to0xcafefeed, andmirror()reads address 8 again. The read data matches, soUVM_CHECKpasses without any message. - update() (150 to 190): only ctrl has a desired value that differs from its mirrored value, so a single write of
2(bit bl_yellow) goes to address 0. - Write to stat (from 190): the register model still sends the write to address
'hcon the bus. Because the field is read-only, the predictor does not change its mirrored value.
Quick Check
Q: Why does update() not write timer[1], even though set(32'hface) was called on it earlier?
Show Answer
predict() and mirror() were called after set(). Both update the desired value along with the mirrored value, so the desired value of timer[1] became 0xcafefeed again. With no difference left, update() skips it.
Where is This Structure Used?
Real projects follow the same layering: a generated register model, a register environment with an adapter and a predictor, and a bus agent connected in the top environment. When the register specification changes, only the generated model is replaced, and the rest of the testbench stays the same.
Common Mistakes
Mistake #1: Reset values that do not match the RTL
In this example, both timers use the same ral_cfg_timer class with a reset value of 32'hCAFE1234, but the RTL resets timer_1 to 32'hface_5678. The test above does not check reset values, but a built-in reset test such as uvm_reg_hw_reset_seq would report a mismatch for timer[1]. Registers with different reset values need their own class or a different reset in configure().
Mistake #2: Forgetting set_sequencer()
Without default_map.set_sequencer(), the register model has no sequencer or adapter for front-door accesses, and the first front-door write() or read() fails, because there is no path to the bus.
Mistake #3: Expecting update() after set() to always write
Wrong: calling set(), then mirror() or predict(), then update()
Right: call update() right after the set() calls that it should apply
Why? mirror() and predict() overwrite the desired value with the current value, so the change made by set() is lost.
Practice Exercise
Challenge: Program a Peak Profile
Write a few lines for main_phase() of a new test that enables the module, selects the peak profile and programs timer[0] with 32'h0102_0304, using set() and a single update() call. Then check the ctrl register in the design with mirror().
Hint
Set the fields mod_en and profile of ctrl, and the field timer of timer[0]. Calling update() on the cfg block writes every register whose desired value changed.
Solution
m_ral_model.cfg.ctrl.mod_en.set (1);
m_ral_model.cfg.ctrl.profile.set (1);
m_ral_model.cfg.timer[0].timer.set (32'h0102_0304);
m_ral_model.cfg.update (status); // Writes ctrl and timer[0]
m_ral_model.cfg.ctrl.mirror (status, UVM_CHECK); // Reads ctrl and compares
The bus shows two writes: 0x9 to address 0 and 0x01020304 to address 4. The mirror() read returns 0x9, so the check passes.
Summary
In this tutorial, you learned:
- A register model is built from register classes, a register block with an address map, and a top block
- The register environment holds the model, a protocol-specific adapter and a predictor
- The top environment connects the monitor to the predictor and gives the map its sequencer and adapter
write()andread()access the design, whileset()only changes the desired valuepredict()andmirror()update both the desired and mirrored values, andupdate()writes only registers whose values differ- The register model sends writes to read-only registers on the bus, but does not change their mirrored value