We already have an idea of how registers are laid out in a memory map from Introduction. So we'll simply use existing UVM RAL (Register Abstraction Layer) classes to define individual fields, registers and register-blocks. A register model is an entity that encompasses and describes the hierarchical structure of class objects for each register and its individual fields. We can perform read and write operations on the design using a register model object.

Every register in the model corresponds to an actual hardware register in the design. There are two kinds of variables inside the register within a model.


Desired Value

This is the value we would like the design to have. In other words, the model has an internal variable to store a desired value that can be updated later in the design. For example, if we want the register REG_STAT in the design to have a value of 0x1234_5678, then the desired value of that register has to be set to 0x1234_5678 within the model and an update task should be called for this to be reflected in the design.


Mirrored Value

Every time a read or a write operation occurs on the design, the mirrored values for that particular register will be updated. Hence the mirrored value in the model is the latest known value in the design. This is very useful because we don't have to issue a read operation everytime we want to know the value of a register in the design.


The desired and mirrored values also exist for every register field.

Creating a register class

Consider that we are building the model step by step, and the first logical step would be to identify all the registers in the design and create an object for each register. From Introduction, we'll first create a register class object for REG_CTL since it's fields are already well defined. Every register object should be inherited from uvm_reg class and every register field should be objects of uvm_reg_field. Note that you don't have to create a separate class for a register field, but simply create an object for every register field as shown in the example below.

register uvm example

class reg_ctl extends uvm_reg;
	rand uvm_reg_field  En;
	rand uvm_reg_field  Mode;
	rand uvm_reg_field  Halt;
	rand uvm_reg_field  Auto;
	rand uvm_reg_field  Speed;


The fields have been declared as rand so that they can be randomized when required and are of type uvm_reg_field. This is only a declaration for all fields, and actual definitions will come later on. Next we have to write the new() function as we normally do with any UVM class. The super call will require three arguments with the first one being the name of the register, the second indicates the size of the register which is 32 bits in our case. The last parameter specifies which functional coverage models are present in the extension of the register abstraction class.

	function new (string name = "reg_ctl");
		super.new (name, 32, UVM_NO_COVERAGE);


Until this point, we have not defined anything related to the register fields and hence, let's do that next in a custom user function called build(). We'll keep the function virtual just in case someone else tries to override this method and call their own version of build().

	virtual function void build ();
		// Create object instance for each field
		this.En     = uvm_reg_field::type_id::create ("En");
		this.Mode   = uvm_reg_field::type_id::create ("Mode");
		this.Halt   = uvm_reg_field::type_id::create ("Halt");
		this.Auto   = uvm_reg_field::type_id::create ("Auto");
		this.Speed  = uvm_reg_field::type_id::create ("Speed");
		// Configure each field
		this.En.configure (this, 1, 0, "RW", 0, 1'h0, 1, 1, 1);
		this.Mode.configure (this, 3, 1, "RW", 0, 3'h2, 1, 1, 1);
		this.Halt.configure (this, 1, 4, "RW", 0, 1'h1, 1, 1, 1);
		this.Auto.configure (this, 1, 5, "RW", 0, 1'h0, 1, 1, 1);
		this.Speed.configure (this, 5, 11, "RW", 0, 5'h1c, 1, 1, 1);


The configure() method is used to define field attributes to the class object and has the following arguments.

function void configure(
                            uvm_reg        parent,
                            int unsigned   size,
                            int unsigned   lsb_pos,
                            string         access,
                            bit            volatile,
                            uvm_reg_data_t reset,
                            bit            has_reset,
                            bit            is_rand,
                            bit            individually_accessible


In a similar way you can define a separate class for each register in the design.

class reg_stat extends uvm_reg;

class reg_dmactl extends uvm_reg;



Creating a register block

After all registers have been defined it needs to be made a part of the register block. For this, we'll define a register block that inherits from uvm_reg_block and create objects of all registers within it. For our purposes let's put only three registers into this block.

class reg_block extends uvm_reg_block;
	rand 	reg_ctl 	m_reg_ctl;
	rand 	reg_stat 	m_reg_stat;
	rand 	reg_inten 	m_reg_inten;
	function new (string name = "reg_block");
		super.new (name, UVM_NO_COVERAGE);
	virtual function void build ();
		// Create an instance for every register
		this.default_map = create_map ("", 0, 4, UVM_LITTLE_ENDIAN, 0);
		this.m_reg_ctl = reg_ctl::type_id::create ("m_reg_ctl", , get_full_name);
		this.m_reg_stat = reg_stat::type_id::create ("m_reg_stat", , get_full_name);
		this.m_reg_inten = reg_inten::type_id::create ("m_reg_inten", , get_full_name);
		// Configure every register instance
		this.m_reg_ctl.configure (this, null, "");
		this.m_reg_stat.configure (this, null, "");
		this.m_reg_inten.configure (this, null, "");
		// Call the build() function to build all register fields within each register
		// Add these registers to the default map
		this.default_map.add_reg (this.m_reg_ctl, `UVM_REG_ADDR_WIDTH'h0, "RW", 0);
		this.default_map.add_reg (this.m_reg_stat, `UVM_REG_ADDR_WIDTH'h4, "RO", 0);
		this.default_map.add_reg (this.m_reg_inten, `UVM_REG_ADDR_WIDTH'h8, "RW", 0);


An object of reg_block class is the register model and can be used to access all registers and perform read and write operations on them. We have only covered the first part of how a register model is defined. But the question of how the actual DUV will be written to and read from has not been discussed yet. To perform register operations, you have to send valid bus transactions via a peripheral bus which will be covered in the next section.