In this post I will describe all the steps I take to startup the uvm_reg register model. If you do things differently please share your view in the comments section below.
If you want to see what is the equivalent of this in e language, with vr_ad library, have a look at this article: How to Startup vr_ad e Library.
Step #1 – Declare Each Register Model
In uvm_reg each register is modeled by a predefined class called uvm_reg. All the fields of the register are modeled with a class called uvm_reg_field. See an example below:
class cfs_dut_reg_ctrl extends uvm_reg; //reserved rand uvm_reg_field reserved; //control for enabling the DUT; rand uvm_reg_field enable; `uvm_object_utils(cfs_dut_reg_config) function new(string name = ""); //specify the name of the register, its width in bits and if it has coverage super.new(name, 32, 1); endfunction virtual function void build(); reserved = uvm_reg_field::type_id::create("reserved"); //specify parent, width, lsb position, rights, volatility, //reset value, has reset, is_rand, individually_accessible reserved.configure(this, 31, 1, "RO", 0, 0, 1, 1, 1); enable = uvm_reg_field::type_id::create("enable"); enable.configure(this, 1, 0, "RW", 0, 0, 1, 1, 1); endfunction endclass
You can control a lot of parameters of the field but most important are:
- field width in bits
- position of its least significant bit
- field access rights
- field reset value
Step #2 – Instantiate the Registers in a Register Block
In uvm_reg the registers can be contained in two entities: a register file or a register block.
A register file (uvm_reg_file) can contain registers and other register files.
A register block (uvm_reg_block) can contain registers, register files, memories and other register blocks.
You can take advantage of organizing your registers with register files but, most of the times, you can use just a register block:
class cfs_dut_reg_block extends uvm_reg_block; //Control register rand cfs_dut_reg_ctrl ctrl; //Status register rand cfs_dut_reg_status status; `uvm_object_utils(cfs_dut_reg_block) function new(string name = ""); super.new(name, UVM_CVR_ALL); ctrl = cfs_dut_reg_ctrl::type_id::create("ctrl"); status = cfs_dut_reg_status::type_id::create("status"); endfunction virtual function void build(); ctrl.configure(this, null, ""); ctrl.build(); status.configure(this, null, ""); status.build(); ... endfunction endclass
Step #3 – Add Registers in a Physical Address Space
In uvm_reg each register is added in an address map with some offset relative to that address map (register offset). After that, the address map is configured with some offset relative to the entire address space accessible through that address map (address map offset).
So when you access a register, its absolute address will be:
address map offset + register offset
Step #3.1 – Create an Address Map
Creating an address map is quite straight forward: you simply take advantage of the method create_map() with which you configure that address map offset:
class cfs_dut_reg_block extends uvm_reg_block; ... virtual function void build(); ... default_map = create_map( .name( "apb_map"), //Configure the "address map offset" .base_addr( 'hF000), .n_bytes( 4), .endian( UVM_BIG_ENDIAN)); default_map.set_check_on_read(1); endfunction endclass
Step #3.2 – Add the Register to the Address Map(s)
In uvm_reg library an address map (class uvm_reg_map) models the register access via a physical interface like APB or UART.
Because of this, all registers which can be accessed via a particular interface must be added to the corresponding address map. This can be done using add_reg() with which you can configure that register offset.
If one register can be accessed via two physical interfaces then it must be added to two address maps.
class cfs_dut_reg_block extends uvm_reg_block; ... virtual function void build(); ... default_map.add_reg(ctrl, 'h00, "RW"); default_map.add_reg(status, 'h04, "RO"); endfunction endclass
Step #4 – Connect the Register Model to an Agent
When it comes to the register model, a minimum requirement is to connect it to the monitoring logic of an agent so that the register model is updated during write operations and checked/updated during read operations.
Connecting the register model to the driving logic of an agent gives us the possibility to separate the test logic from the physical protocol. This means that in the test code you can write actions like “read register” or “write register” without knowing/caring how this translate on a physical interface.
Let’s see how these two connections can be implemented.
Step #4.1 – Connect the Register Model to Monitoring Logic
In order to connect the register model to the monitoring logic you first need to tell uvm_reg how to transform an APB transfer (which can have many implementations) to something that uvm_reg can understand, which is an uvm_reg_bus_op class.
So for this you need to create an adapter, which extends from uvm_reg_adapter, and in this adapter you have to implement this transformation in bus2reg() method.
One important thing here: you must be careful that when you develop the physical monitor, the collected information must stay in a class which extends from uvm_sequence_item. Otherwise you will not be able to connect the monitor to the register model.
class cfs_dut_reg_adapter extends uvm_reg_adapter; `uvm_object_utils(cfs_dut_reg_adapter) function new(string name = "cfs_dut_reg_adapter"); super.new(name); endfunction virtual function void bus2reg(uvm_sequence_item bus_item, ref uvm_reg_bus_op rw); acme_apb_mon_transfer transfer; if($cast(transfer, bus_item)) begin if(transfer.direction == APB_WRITE) begin rw.kind = UVM_WRITE; end else begin rw.kind = UVM_READ; end rw.addr = transfer.address; rw.data = transfer.data; rw.status = UVM_IS_OK; end else begin `uvm_fatal(get_name(), $sformatf("Could not cast to acme_apb_mon_transfer: %s", bus_item.get_type_name())) end endfunction endclass
Next, you have to create an instance of a predictor class (uvm_reg_predictor) which needs to know several important things:
- what information was collected by the APB monitor from the bus
- what adapter can use to transform the APB transfer to an uvm_reg_bus_op
- what address map is accessible via that APB interface
These can be done in the following manner:
class cfs_dut_env extends uvm_env; ... //register block cfs_dut_reg_block reg_block; //APB register adapter cfs_dut_reg_adapter adapter; //APB register predictor uvm_reg_predictor#(acme_apb_mon_transfer) predictor; `uvm_component_utils(cfs_dut_env) function new(string name = "cfs_dut_env", uvm_component parent); super.new(name, parent); endfunction virtual function void build_phase(uvm_phase phase); super.build_phase(phase); //create all the elements of the environment reg_block = cfs_dut_reg_block::type_id::create("reg_block"); adapter = cfs_dut_reg_adapter::type_id::create("adapter"); predictor = uvm_reg_predictor#(acme_apb_mon_transfer)::type_id::create("predictor", this); ... endfunction virtual function void connect_phase(uvm_phase phase); super.connect_phase(phase); //configure the predictor with an address map and an adapter predictor.map = reg_block.default_map; predictor.adapter = adapter; //connect the APB monitor with the predictor apb_agent.monitor.output_port.connect(predictor.bus_in); ... endfunction endclass
If you need to do some filtering on the information coming from the monitor, you can create your own custom predictor, by extending a class from uvm_reg_predictor and overriding the write() method of the bus_in port, but in most of the cases the default implementation would do just fine.
Step #4.2 – Connect the Register Model to Driving Logic
In order to connect the register model to the driving logic you first need to tell uvm_reg how to transform an uvm_reg_bus_op class to an APB sequence item which the APB sequencer can understand.
So for this you need to implement a different method in the adapter called reg2bus().
class cfs_dut_reg_adapter extends uvm_reg_adapter; ... virtual function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw); acme_apb_drv_transfer transfer = acme_apb_drv_transfer::type_id::create("transfer"); if(rw.kind == UVM_WRITE) begin transfer.direction = APB_WRITE; end else begin transfer.direction = APB_READ; end transfer.data = rw.data; transfer.address = rw.addr; return transfer; endfunction endclass
Next, you have to tell the predictor what sequencer it can use in order to send register accesses on the APB bus:
class cfs_dut_env extends uvm_env; ... virtual function void connect_phase(uvm_phase phase); ... //required to start physical register accesses using the registers reg_block.default_map.set_sequencer(apb_agent.sequencer, adapter); ... endfunction endclass
Step #5 – Miscellaneous Configurations/Actions
Some other small configurations/actions that you need to do are the following:
- build the register model
- lock the register model
- trigger an initial reset to start from a known state
Here is how these actions can be done:
class cfs_dut_env extends uvm_env; virtual function void build_phase(uvm_phase phase); ... //build the register model reg_block.build(); //lock the register model reg_block.lock_model(); //trigger an initial reset to start from a known state reg_block.reset("HARD"); endfunction ... endclass
At this point your register model is up and running.
Based on your DUT functionality there are more things that you might use from the uvm_reg. Here are some uvm_reg features that I used so far. I will try to explain them all in next posts:
- uvm_reg_field callbacks for triggering different actions when there is an operation performed on a register field
- multiple reset types support with different reset values per reset type, per register field
Thanks for well structured guide for reg model implementation. I have one small remark/doubt: Shouldn’t we do register model lock on level where top reg block is instantiated ? As I understood, it work recursively from top to bottom, so if you do lock within reg block build function, and then reuse that block as part of bigger block, it may not work.
Can you let us know, how to lock for reg block in reuse?
I’m confused that the UVM LRM said that “RO” have to be “un-modelled”. but some educational site of UVM used RAL model with “RO” modelled. What usage does the correct? Do I have to model or not about “RO” in RAL model.?
Read-only register fields can be modeled in your environment. For example, status registers must be implemented as RO but you still have to model their value.