What is a register model ?

A register model, provide a structured and standardized way to model and verify the registers and memory-mapped structures within a digital design. It consists of a hierarchy of blocks represented by UVM class objects that are structured equivalent to the registers and memory in design.

uvm_ral_overview

Since typical designs contain hundreds of registers, register model generation is usually done by custom scripts or standard software tools like RalGen from Synopsys or RGM from Cadence. These tools take the register specification as an input such as an IPXACT XML file and generates a SystemVerilog file which contains fields and registers specified using standard UVM register classes.


// Sample IPXACT register specification

<spirit:usage>register</spirit:usage>
	<spirit:register>
    	<spirit:name>REG_CTL</spirit:name>
      	<spirit:addressOffset>0x0</spirit:addressOffset>
      	<spirit:size>32</spirit:size>
      	<spirit:reset>
         	<spirit:value>0x2A</spirit:value>
      	</spirit:reset>
      	<spirit:access>read-write</spirit:access>
      	<spirit:field>
          <spirit:name>EN</spirit:name>
          <spirit:description>Enable the module</spirit:description>
          <spirit:bitOffset>0</spirit:bitOffset>
          <spirit:bitWidth>1</spirit:bitWidth>
          <spirit:access>read-write</spirit:access>
      </spirit:field>

      ...      

UVM RAL Classes

uml_uvm_ral_hier
ClassDescription
uvm_reg_fieldUsed for register field implementation
uvm_regUsed to implement design register
uvm_reg_fileUsed to collect a group of registers
uvm_reg_mapRepresents an address map
uvm_memUsed to represent memory in design
uvm_reg_blockContainer class to store registers, maps and memories

uvm_reg_field

uvm_reg_field is a class that is used to model individual fields within a register. Fields in a register represent specific bits or groups of bits that have distinct functionalities, access permissions, reset values, and other attributes.

uml_uvm_reg_field

These are some of the most commonly used methods in uvm_reg_field. Please refer to the UVM reference manual to see the full set of methods.


	// Configure the register field class
	extern function void configure(uvm_reg        parent, 		// Parent register handle
                                   int unsigned   size, 		// Bit-width of the field
                                   int unsigned   lsb_pos, 		// LSB index of the field in register
                                   string         access, 		// Read/read-write/etc access policy
                                   bit            volatile, 		
                                   uvm_reg_data_t reset,
                                   bit            has_reset, 	
                                   bit            is_rand,
                                   bit            individually_accessible); 


	// Get width of the field, in number of bits
	extern virtual function int unsigned get_n_bits();

	// Returns index of LSB of the field in the register that instantiates it
	extern virtual function int unsigned get_lsb_pos();

	// Set desired value in the regmodel for this field
	extern virtual function void set(uvm_reg_data_t  value,
                                    string          fname = "",
                                    int             lineno = 0);

	// Get desired value in regmodel for this field
	extern virtual function uvm_reg_data_t get(string fname = "",
                                              int    lineno = 0);

	// Get mirrored value in regmodel for this field
	extern virtual function uvm_reg_data_t get_mirrored_value(string fname = "",
                                               				 int    lineno = 0);

Here is an example of how user register field of type uvm_reg_field is instantiated and configured inside a register.


	// Declare register field handle
	uvm_reg_field 	m_enable;

	// Create an instance and configure the register field handle
	m_enable = uvm_reg_field::type_id::create("m_enable");
	m_enable.configure(this, 1, 0, "RW", 0, 1'h0, 1, 1, 1);

uvm_reg

uvm_reg is a base class provided by the UVM library that is used to model registers, and user defined classes are extended from this base class.

uml_uvm_reg

These are some of the most commonly used methods in uvm_reg.


   // Get fields of this register as a queue
   extern virtual function void get_fields (ref uvm_reg_field fields[$]);

   // Get a particular field by name
   extern virtual function uvm_reg_field get_field_by_name(string name);

   // Get address of this register
   extern virtual function uvm_reg_addr_t get_address (uvm_reg_map map = null);

   // Set desired value in regmodel
   extern virtual function void set (uvm_reg_data_t  value,
                                     string          fname = "",
                                     int             lineno = 0);

   // Get desired value in regmodel
   extern virtual function uvm_reg_data_t  get(string  fname = "",
                                               int     lineno = 0);

   // Get last known design value 
   extern virtual function uvm_reg_data_t  get_mirrored_value(string  fname = "",
                                               int     lineno = 0);

   // Issue a register write with the given value
   extern virtual task write(output uvm_status_e      status,
                             input  uvm_reg_data_t    value,
                             input  uvm_door_e        path = UVM_DEFAULT_DOOR,
                             input  uvm_reg_map       map = null,
                             input  uvm_sequence_base parent = null,
                             input  int               prior = -1,
                             input  uvm_object        extension = null,
                             input  string            fname = "",
                             input  int               lineno = 0);

   // Issue a register read and get value into the given variable
   extern virtual task read(output uvm_status_e      status,
                            output uvm_reg_data_t    value,
                            input  uvm_door_e        path = UVM_DEFAULT_DOOR,
                            input  uvm_reg_map       map = null,
                            input  uvm_sequence_base parent = null,
                            input  int               prior = -1,
                            input  uvm_object        extension = null,
                            input  string            fname = "",
                            input  int               lineno = 0);

   // Update design if desired value is not same as mirrored value
   extern virtual task update(output uvm_status_e      status,
                              input  uvm_door_e        path = UVM_DEFAULT_DOOR,
                              input  uvm_reg_map       map = null,
                              input  uvm_sequence_base parent = null,
                              input  int               prior = -1,
                              input  uvm_object        extension = null,
                              input  string            fname = "",
                              input  int               lineno = 0);

   // Update regmodel with mirrored value
   extern virtual task mirror(output uvm_status_e      status,
                              input uvm_check_e        check  = UVM_NO_CHECK,
                              input uvm_door_e         path = UVM_DEFAULT_DOOR,
                              input uvm_reg_map        map = null,
                              input uvm_sequence_base  parent = null,
                              input int                prior = -1,
                              input  uvm_object        extension = null,
                              input string             fname = "",
                              input int                lineno = 0);

Here is an example of a user register extended from uvm_reg base class.


class reg_ctl extends uvm_reg;
	rand uvm_reg_field  En;

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

	virtual function void build ();	
		// Create object instance for each field
		this.En     = uvm_reg_field::type_id::create ("En");
		
		// Configure each field
		this.En.configure (this, 1, 0, "RW", 0, 1'h0, 1, 1, 1);
	endfunction
endclass

uvm_reg_block

A uvm_reg_block can contain registers, register files, memories and sub-blocks.

uml_uvm_reg_block

	// Get queue of registers
   extern virtual function void get_registers (ref uvm_reg regs[$],
                                               input uvm_hier_e hier=UVM_HIER);

	// Get register block by name
  extern virtual function uvm_reg_block get_block_by_name (string name);  

	// Get register by name
   extern virtual function uvm_reg get_reg_by_name (string name);

	// Get field by name
   extern virtual function uvm_reg_field get_field_by_name (string name);

	// Update registers in this reg block
   extern virtual task update(output uvm_status_e       status,
                              input  uvm_door_e         path = UVM_DEFAULT_DOOR,
                              input  uvm_sequence_base  parent = null,
                              input  int                prior = -1,
                              input  uvm_object         extension = null,
                              input  string             fname = "",
                              input  int                lineno = 0);

What are desired and mirrored values ?

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.

design-register-model-desired-mirrored-values

Desired value is what we 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.

desired-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.

mirrored-value

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

Example

Lets create a register class object for REG_CTL. 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);
	endfunction

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);
	endfunction
endclass

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;
	...
endclass

class reg_dmactl extends uvm_reg;
	...
endclass

...

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);
	endfunction
	
	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
		this.m_reg_ctl.build();
		this.m_reg_stat.build();
		this.m_reg_inten.build();
		
		// 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);
	endfunction
endclass

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.