In the previous few articles, we have seen what a register model is and how it can be used to access registers in a given design. Let us see a complete example of how such a model can be written for a given design, how it can be integrated into the environment and how it can be used to write and read into design fields.
Click here to refresh the concept on register models !
Design
The following design has the following registers and fields that are accessible through an APB interface. The design essentially represents a traffic light controller which can be configured by writing into certain control registers.
The ctl register contains fields to start the module, and configure it to be in the blink yellow or blink red mode. The state register is read-only and returns current state of the design - yellow, red or green. The two timer registers stores the time between transition from each state. The profile bit allows the user to choose between the two programmed timer values. This can be useful for peak and off-peak times.

This is not a complete design since our purpose is simply to show how registers in this design can be read/written using a UVM register model. All the signals listed as the module ports belong to APB specification.
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
Interface
Let us declare an interface with signals in the APB protocol and this interface can be passed to the testbench as a virtual interface for the driver to drive some values to the design. To keep things simple, let us not declare clocking blocks and modports, 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
A register model for the design registers discussed above can be developed as shown. In typical projects, this would be an automated flow where scripts read register specification formats like IPXACT and convert them into a register model. The register model can then be directly plugged into the register environment or any other component as desired.
// 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
Register Environment
Now that the register model is available, next step is to connect all components required for the register model to work in a separate environment. This will enable the environment to be directly plugged into the testbench and hides the connection between predictor, adapter and register model from the top level environment.
Since the adapter is dependent on the bus protocol in use, we need to derive a custom class from uvm_reg_adapter
and let's name it reg2apb_adapter. The two functions reg2bus and bus2reg are defined here so that register items are converted to APB bus packets and vice versa.
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
APB Agent Example
AMBA APB is a bus protocol used for low-bandwidth peripheral accesses and a typical application of the protocol is in the access of control registers in a device. The code shown below is a simple model of the APB agent required for our purpose. It does not handle many of the features in an 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
Now that we have the register environment and an APB agent, we can create a top level environment where these two can be placed and connected.
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 helps to bring the DUT out of reset. It gets the virtual interface handle from uvm_config_db
and toggles the DUT reset input directly from the sequence. Register acceses can be written inside a similar sequence, but for our purpose let us simply call them from the test class.
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
The first part of creating a test library is to develop a base test that configures the test and the environment. All other specific tests can be derived from this base test so that it can focus on starting stimulus sequences.
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 ---
