uvm_object has many common functions like print, copy and compare that are available to all its child classes and can be used out of the box if UVM automation macros are used inside the class definition. In a previous article, copy, do_copy and use of automation macros to print were discussed.

Using automation macros

UVM automation macros can be used for automatic implementation of copy, print, compare and more. In this example, a class called Packet is defined with a variable called m_addr that is registered with automation macros `uvm_field_int to be included for all the default functions like print, copy, compare, etc. An object of this class is used inside another class Object which has a bunch of other variables of different data types that are also registered using appropriate macros. For example, m_bool is of enum type and is registered with `uvm_field_enum macro.

  
  
typedef enum {FALSE, TRUE} e_bool;

class Packet extends uvm_object;
  rand bit[15:0] 	m_addr;
  
  virtual function string convert2string();
    string contents;
    contents = $sformatf("m_addr=0x%0h", m_addr);
  endfunction

  `uvm_object_utils_begin(Packet)
  	`uvm_field_int(m_addr, UVM_DEFAULT)
  `uvm_object_utils_end
  
  function new(string name = "Packet");
    super.new(name);
  endfunction
endclass

class Object extends uvm_object;
  rand e_bool 				m_bool;
  rand bit[3:0] 			m_mode;
  string 					m_name;
  rand Packet 				m_pkt;
  
  function new(string name = "Object");
    super.new(name);
    m_name = name;
    m_pkt = Packet::type_id::create("m_pkt");
    m_pkt.randomize();
  endfunction
  
  `uvm_object_utils_begin(Object)
  	`uvm_field_enum(e_bool, m_bool, UVM_DEFAULT)
  	`uvm_field_int (m_mode, 		UVM_DEFAULT)
  	`uvm_field_string(m_name, 		UVM_DEFAULT)
  	`uvm_field_object(m_pkt, 		UVM_DEFAULT)
  `uvm_object_utils_end
endclass

  

Let us create a base class and create two objects obj1 and obj2, randomize both and print them. Then compare both objects to see what the result is. Then copy each variable from obj2 from obj1 including the nested class object of type Packet until the comparison errors are resolved.

  
  
class base_test extends uvm_test;
  `uvm_component_utils(base_test)
  function new(string name = "base_test", uvm_component parent=null);
    super.new(name, parent);
  endfunction
  
  function void build_phase(uvm_phase phase);
  	// Create two objects, randomize them and print the contents
    Object obj1 = Object::type_id::create("obj1");
    Object obj2 = Object::type_id::create("obj2");
    obj1.randomize();
    obj1.print();
    obj2.randomize();
    obj2.print();
    
    _compare(obj1, obj2);
   
      `uvm_info("TEST", "Copy m_bool", UVM_LOW)
    obj2.m_bool = obj1.m_bool;
        `uvm_info("TEST", $sformatf("Obj2.print: %s", obj2.convert2string()), UVM_LOW)
    _compare(obj1, obj2);
      
      `uvm_info("TEST", "Copy m_mode", UVM_LOW)
    obj2.m_mode = obj1.m_mode;
        `uvm_info("TEST", $sformatf("Obj2.print: %s", obj2.convert2string()), UVM_LOW)
    _compare(obj1, obj2);
    
      `uvm_info("TEST", "Copy m_name", UVM_LOW)
    obj2.m_name = obj1.m_name;
        `uvm_info("TEST", $sformatf("Obj2.print: %s", obj2.convert2string()), UVM_LOW)
    _compare(obj1, obj2);
      
      `uvm_info("TEST", "Copy m_pkt.m_addr", UVM_LOW)
    obj2.m_pkt.m_addr = obj1.m_pkt.m_addr;
    	`uvm_info("TEST", $sformatf("Obj2.print: %s", obj2.convert2string()), UVM_LOW)
    _compare(obj1, obj2);
  endfunction
  
  function void _compare(Object obj1, obj2);
    if (obj2.compare(obj1))
      `uvm_info("TEST", "obj1 and obj2 are same", UVM_LOW)
    else
      `uvm_info("TEST", "obj1 and obj2 are different", UVM_LOW)
  endfunction
endclass

module tb;
	initial begin
		run_test("base_test");
	end
endmodule

  
Simulation Log

UVM_INFO @ 0: reporter [RNTST] Running test base_test...
----------------------------------
Name        Type      Size  Value 
----------------------------------
obj1        Object    -     @1903 
  m_bool    e_bool    32    TRUE  
  m_mode    integral  4     'h9   
  m_name    string    4     obj1  
  m_pkt     Packet    -     @1905 
    m_addr  integral  16    'h3cb6
----------------------------------
----------------------------------
Name        Type      Size  Value 
----------------------------------
obj2        Object    -     @1907 
  m_bool    e_bool    32    FALSE 
  m_mode    integral  4     'hf   
  m_name    string    4     obj2  
  m_pkt     Packet    -     @1908 
    m_addr  integral  16    'h64c1
----------------------------------
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_comparer.svh(351) @ 0: reporter [MISCMP] Miscompare for obj2.m_bool: lhs = FALSE : rhs = TRUE
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_comparer.svh(382) @ 0: reporter [MISCMP] 1 Miscompare(s) for object [email protected] vs. [email protected]
UVM_INFO testbench.sv(71) @ 0: uvm_test_top [TEST] obj1 and obj2 are different
UVM_INFO testbench.sv(73) @ 0: uvm_test_top [TEST] Copy m_bool
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_comparer.svh(351) @ 0: reporter [MISCMP] Miscompare for obj2.m_mode: lhs = 'hf : rhs = 'h9
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_comparer.svh(382) @ 0: reporter [MISCMP] 1 Miscompare(s) for object [email protected] vs. [email protected]
UVM_INFO testbench.sv(78) @ 0: uvm_test_top [TEST] obj1 and obj2 are different
UVM_INFO testbench.sv(80) @ 0: uvm_test_top [TEST] Copy m_mode
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_comparer.svh(351) @ 0: reporter [MISCMP] Miscompare for obj2.m_name: lhs = "obj2" : rhs = "obj1"
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_comparer.svh(382) @ 0: reporter [MISCMP] 1 Miscompare(s) for object [email protected] vs. [email protected]
UVM_INFO testbench.sv(85) @ 0: uvm_test_top [TEST] obj1 and obj2 are different
UVM_INFO testbench.sv(87) @ 0: uvm_test_top [TEST] Copy m_name
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_comparer.svh(351) @ 0: reporter [MISCMP] Miscompare for obj2.m_pkt.m_addr: lhs = 'h64c1 : rhs = 'h3cb6
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_comparer.svh(382) @ 0: reporter [MISCMP] 1 Miscompare(s) for object [email protected] vs. [email protected]
UVM_INFO testbench.sv(92) @ 0: uvm_test_top [TEST] obj1 and obj2 are different
UVM_INFO testbench.sv(94) @ 0: uvm_test_top [TEST] Copy m_pkt.m_addr
UVM_INFO testbench.sv(97) @ 0: uvm_test_top [TEST] obj1 and obj2 are same
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_report_server.svh(847) @ 0: reporter [UVM/REPORT/SERVER] 

Automation macros introduce a lot of additional code and is not generally recommended

Using do_compare

Another way is for the user to implement do_compare method inside the child class and compare the values of each variable and return the result. Note in the following example that uvm_object_utils_* macros are not used and hence the print() method we used above will not work as is. Instead another method called convert2string is implemented for both classes so that it returns the contents in a string format when called.

Just like the way print calls do_print method, a compare calls the do_compare method and implementation of the function is all that is required to be done.

  
  
typedef enum {FALSE, TRUE} e_bool;

class Packet extends uvm_object;
  rand bit[15:0] 	m_addr;
  
  virtual function string convert2string();
    string contents;
    contents = $sformatf("m_addr=0x%0h", m_addr);
    return contents;
  endfunction

  `uvm_object_utils(Packet)
  virtual function bit do_compare(uvm_object rhs, uvm_comparer comparer);
    bit res;
    Packet _pkt;
    
    $cast(_pkt, rhs);
    super.do_compare(_pkt, comparer);
    
    res = 	super.do_compare(_pkt, comparer) &
			m_addr == _pkt.m_addr;   			
    `uvm_info(get_name(), $sformatf("In Packet::do_compare(), res=%0b", res), UVM_LOW)
    return res;
  endfunction
  
  function new(string name = "Packet");
    super.new(name);
  endfunction
endclass

class Object extends uvm_object;
  rand e_bool 				m_bool;
  rand bit[3:0] 			m_mode;
  string 					m_name;
  rand Packet 				m_pkt;
  
  function new(string name = "Object");
    super.new(name);
    m_name = name;
    m_pkt = Packet::type_id::create("m_pkt");
    m_pkt.randomize();
  endfunction
  
  // This task is used to print contents
  virtual function string convert2string();
    string contents = "";
    $sformat(contents, "%s m_name=%s", contents, m_name);
    $sformat(contents, "%s m_bool=%s", contents, m_bool.name());
    $sformat(contents, "%s m_mode=0x%0h", contents, m_mode);
    $sformat(contents, "%s %s", contents, m_pkt.convert2string()); 
    return contents;
  endfunction
  
  `uvm_object_utils(Object)
  
  // "rhs" does not contain m_bool, m_mode, etc since its a parent
  // handle. So cast into child data type and access using child handle
  // Copy each field from the casted handle into local variables
  virtual function bit do_compare(uvm_object rhs, uvm_comparer comparer);
    bit res;
    Object _obj;
    $cast(_obj, rhs);
    res = 	super.do_compare(_obj, comparer) &
    		m_name == _obj.m_name &
    		m_mode == _obj.m_mode &
    		m_bool == _obj.m_bool &
    		m_pkt.do_compare(_obj.m_pkt, comparer);
    
    `uvm_info(get_name(), $sformatf("In Object::do_compare(), res=%0b", res), UVM_LOW)
    return res;
  endfunction
endclass

  

UVM automation macros were used in the previous example and hence the print method implementation was already done and available to be used. However, when automation is not used, user has to implement either do_print or atleast fill the convert2string function so that contents can be printed. We have chosen to do the latter as evident in the code above. So convert2string function is called in a UVM info statement instead of calling print.

Then compare the variables, copy each variable from obj1 into obj2 and keep comparing until both objects are the same.

  
  
class base_test extends uvm_test;
  `uvm_component_utils(base_test)
  function new(string name = "base_test", uvm_component parent=null);
    super.new(name, parent);
  endfunction
  
  function void build_phase(uvm_phase phase);
    Object obj1 = Object::type_id::create("obj1");
    Object obj2 = Object::type_id::create("obj2");
    obj1.randomize();
    `uvm_info("TEST", $sformatf("Obj1.print: %s", obj1.convert2string()), UVM_LOW)
    obj2.randomize();
    `uvm_info("TEST", $sformatf("Obj2.print: %s", obj2.convert2string()), UVM_LOW)
    
    _compare(obj1, obj2);
   
      `uvm_info("TEST", "Copy m_bool", UVM_LOW)
    obj2.m_bool = obj1.m_bool;
        `uvm_info("TEST", $sformatf("Obj2.print: %s", obj2.convert2string()), UVM_LOW)
    _compare(obj1, obj2);
      
      `uvm_info("TEST", "Copy m_mode", UVM_LOW)
    obj2.m_mode = obj1.m_mode;
        `uvm_info("TEST", $sformatf("Obj2.print: %s", obj2.convert2string()), UVM_LOW)
    _compare(obj1, obj2);
    
      `uvm_info("TEST", "Copy m_name", UVM_LOW)
    obj2.m_name = obj1.m_name;
        `uvm_info("TEST", $sformatf("Obj2.print: %s", obj2.convert2string()), UVM_LOW)
    _compare(obj1, obj2);
      
      `uvm_info("TEST", "Copy m_pkt.m_addr", UVM_LOW)
    obj2.m_pkt.m_addr = obj1.m_pkt.m_addr;
    	`uvm_info("TEST", $sformatf("Obj2.print: %s", obj2.convert2string()), UVM_LOW)
    _compare(obj1, obj2);

  endfunction
  
  function void _compare(Object obj1, obj2);
    if (obj2.compare(obj1))
      `uvm_info("TEST", "obj1 and obj2 are same", UVM_LOW)
    else
      `uvm_info("TEST", "obj1 and obj2 are different", UVM_LOW)
  endfunction
endclass

module tb;
	initial begin
		run_test("base_test");
	end
endmodule

  
Simulation Log

UVM_INFO @ 0: reporter [RNTST] Running test base_test...
UVM_INFO testbench.sv(96) @ 0: uvm_test_top [TEST] Obj1.print:  m_name=obj1 m_bool=TRUE m_mode=0x9 m_addr=0x3cb6
UVM_INFO testbench.sv(98) @ 0: uvm_test_top [TEST] Obj2.print:  m_name=obj2 m_bool=FALSE m_mode=0xf m_addr=0x64c1
UVM_INFO testbench.sv(30) @ 0: reporter [m_pkt] In Packet::do_compare(), res=0
UVM_INFO testbench.sv(77) @ 0: reporter [obj2] In Object::do_compare(), res=0
UVM_INFO testbench.sv(128) @ 0: uvm_test_top [TEST] obj1 and obj2 are different
UVM_INFO testbench.sv(102) @ 0: uvm_test_top [TEST] Copy m_bool
UVM_INFO testbench.sv(104) @ 0: uvm_test_top [TEST] Obj2.print:  m_name=obj2 m_bool=TRUE m_mode=0xf m_addr=0x64c1
UVM_INFO testbench.sv(30) @ 0: reporter [m_pkt] In Packet::do_compare(), res=0
UVM_INFO testbench.sv(77) @ 0: reporter [obj2] In Object::do_compare(), res=0
UVM_INFO testbench.sv(128) @ 0: uvm_test_top [TEST] obj1 and obj2 are different
UVM_INFO testbench.sv(107) @ 0: uvm_test_top [TEST] Copy m_mode
UVM_INFO testbench.sv(109) @ 0: uvm_test_top [TEST] Obj2.print:  m_name=obj2 m_bool=TRUE m_mode=0x9 m_addr=0x64c1
UVM_INFO testbench.sv(30) @ 0: reporter [m_pkt] In Packet::do_compare(), res=0
UVM_INFO testbench.sv(77) @ 0: reporter [obj2] In Object::do_compare(), res=0
UVM_INFO testbench.sv(128) @ 0: uvm_test_top [TEST] obj1 and obj2 are different
UVM_INFO testbench.sv(112) @ 0: uvm_test_top [TEST] Copy m_name
UVM_INFO testbench.sv(114) @ 0: uvm_test_top [TEST] Obj2.print:  m_name=obj1 m_bool=TRUE m_mode=0x9 m_addr=0x64c1
UVM_INFO testbench.sv(30) @ 0: reporter [m_pkt] In Packet::do_compare(), res=0
UVM_INFO testbench.sv(77) @ 0: reporter [obj2] In Object::do_compare(), res=0
UVM_INFO testbench.sv(128) @ 0: uvm_test_top [TEST] obj1 and obj2 are different
UVM_INFO testbench.sv(117) @ 0: uvm_test_top [TEST] Copy m_pkt.m_addr
UVM_INFO testbench.sv(119) @ 0: uvm_test_top [TEST] Obj2.print:  m_name=obj1 m_bool=TRUE m_mode=0x9 m_addr=0x3cb6
UVM_INFO testbench.sv(30) @ 0: reporter [m_pkt] In Packet::do_compare(), res=1
UVM_INFO testbench.sv(77) @ 0: reporter [obj2] In Object::do_compare(), res=1
UVM_INFO testbench.sv(126) @ 0: uvm_test_top [TEST] obj1 and obj2 are same
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_report_server.svh(847) @ 0: reporter [UVM/REPORT/SERVER] 

Design

  
  
module single_port_sync_ram 
  # (parameter ADDR_WIDTH = 4,
     parameter DATA_WIDTH = 32,
     parameter DEPTH = 16 
    )
  
  ( 	input 					clk,
   		input [ADDR_WIDTH-1:0]	addr,
   		inout [DATA_WIDTH-1:0]	data,
   		input 					cs,
   		input 					we,
   		input 					oe
  );
  
  reg [DATA_WIDTH-1:0] 	tmp_data;
  reg [DATA_WIDTH-1:0] 	mem [DEPTH];
  
  always @ (posedge clk) begin
    if (cs & we)
      mem[addr] <= data;
  end
  
  always @ (posedge clk) begin
    if (cs & !we)
    	tmp_data <= mem[addr];
  end
  
  assign data = cs & oe & !wr ? tmp_data : 'hz;
endmodule

  
Single Port RAM with async read and write

Testbench

  
  
module tb;
  parameter ADDR_WIDTH = 4;
  parameter DATA_WIDTH = 16;
  parameter DEPTH = 16;
  
  reg clk;
  reg cs;
  reg we;
  reg oe;
  reg [ADDR_WIDTH-1:0] addr;
  wire [DATA_WIDTH-1:0] data;
  reg [DATA_WIDTH-1:0] tb_data;
  
  single_port_sync_ram #(.DATA_WIDTH(DATA_WIDTH)) u0
  ( 	.clk(clk),
                        	.addr(addr),
                        	.data(data),
                        	.cs(cs),
   							.we(we),
   							.oe(oe)
                         );
  
  
  always #10 clk = ~clk;
  assign data = !oe ? tb_data : 'hz;
  
  initial begin
    {clk, cs, we, addr, tb_data, oe} <= 0;
    
    repeat (2) @ (posedge clk);
    
    for (integer i = 0; i < 2**ADDR_WIDTH; i= i+1) begin
      repeat (1) @(posedge clk) addr <= i; we <= 1; cs <=1; oe <= 0; tb_data <= $random;
    end
    
    for (integer i = 0; i < 2**ADDR_WIDTH; i= i+1) begin
      repeat (1) @(posedge clk) addr <= i; we <= 0; cs <= 1; oe <= 1;
    end
    
    #20 $finish;
  end
endmodule

  

We have seen in previous sessions that by instantiating the environment inside each testcase, we have the ability to tweak environment parameters to enable multiple scenarios. A test writer may not have the knowledge of how verification components are hooked-up with each other or how they interact with each other. Hence, it becomes important to hide those details and only present the test writer with ways to tweak the environment for each testcase and here are a few ways to do that.

What is testbench top module ?

All verification components, interfaces and DUT are instantiated in a top level module called testbench. It is a static container to hold everything required to be simulated and becomes the root node in the hierarchy. This is usually named tb or tb_top although it can assume any other name.

top-module

What is a testcase ?

A testcase is a pattern to check and verify specific features and functionalities of a design. A verification plan lists all the features and other functional items that needs to be verified, and the tests neeeded to cover each of them.

A lot of different tests, hundreds or even more, are typically required to verify complex designs.

Instead of writing the same code for different testcases, we put the entire testbench into a container called an Environment, and use the same environment with a different configuration for each test. Each testcase can override, tweak knobs, enable/disable agents, change variable values in the configuration table and run different sequences on many sequencers in the verification environment.

uvm_test

Steps to write a UVM Test

1. Create a custom class inherited from uvm_test, register it with factory and call function new
  
  
// Step 1: Declare a new class that derives from "uvm_test"
// my_test is user-given name for this class that has been derived from "uvm_test"
class my_test extends uvm_test;
 
    // [Recommended] Makes this test more re-usable
    `uvm_component_utils (my_test)
 
    // This is standard code for all components
    function new (string name = "my_test", uvm_component parent = null);
      super.new (name, parent);
    endfunction
 
    // Code for rest of the steps come here
endclass

  
2. Declare other environments and verification components and build them
  
  
	  // Step 2: Declare other testbench components - my_env and my_cfg are assumed to be defined
      my_env   m_top_env;              // Testbench environment that contains other agents, register models, etc
      my_cfg   m_cfg0;                 // Configuration object to tweak the environment for this test
      
      // Instantiate and build components declared above
      virtual function void build_phase (uvm_phase phase);
         super.build_phase (phase);

         // [Recommended] Instantiate components using "type_id::create()" method instead of new()
         m_top_env  = my_env::type_id::create ("m_top_env", this);
         m_cfg0     = my_cfg::type_id::create ("m_cfg0", this);
      
         // [Optional] Configure testbench components if required, get virtual interface handles, etc
         set_cfg_params ();

         // [Recommended] Make the cfg object available to all components in environment/agent/etc
         uvm_config_db #(my_cfg) :: set (this, "m_top_env.my_agent", "m_cfg0", m_cfg0);
      endfunction

  
3. Print UVM topology if required
  
  
	  // [Recommended] By this phase, the environment is all set up so its good to just print the topology for debug
      virtual function void end_of_elaboration_phase (uvm_phase phase);
         uvm_top.print_topology ();
      endfunction

  
4. Start a virtual sequence
  
  
      // Start a virtual sequence or a normal sequence for this particular test
      virtual task run_phase (uvm_phase phase);
      
      	// Create and instantiate the sequence
      	my_seq m_seq = my_seq::type_id::create ("m_seq");
      	
      	// Raise objection - else this test will not consume simulation time*
      	phase.raise_objection (this);
      	
      	// Start the sequence on a given sequencer
      	m_seq.start (m_env.seqr);
      	
      	// Drop objection - else this test will not finish
      	phase.drop_objection (this);
      endtask

  

How to run a UVM test

A test is usually started within testbench top by a task called run_test.

This global task should be supplied with the name of user-defined UVM test that needs to be started. If the argument to run_test is blank, it is necessary to specify the testname via command-line options to the simulator using +UVM_TESTNAME.

  
  
// Specify the testname as an argument to the run_test () task
initial begin
   run_test ("base_test");
end

  

Definition for run_test is given below.

  
  
// This is a global task that gets the UVM root instance and 
// starts the test using its name. This task is called in tb_top
task run_test (string test_name="");
  uvm_root top;
  uvm_coreservice_t cs;
  cs = uvm_coreservice_t::get();
  top = cs.get_root();
  top.run_test(test_name);
endtask

  

How to run any UVM test

This method is preferred because it allows more flexibility to choose different tests without modifying testbench top every time you want to run a different test. It also avoids the need for recompilation since contents of the file is not updated.

If +UVM_TESTNAME is specified, the UVM factory creates a component of the given test type and starts its phase mechanism. If the specified test is not found or not created by the factory, then a fatal error occurs. If no test is specified via command-line and the argument to the run_test() task is blank, then all the components constructed before the call to run_test() will be cycled through their simulation phases.

  
  
// Pass the DEFAULT test to be run if nothing is provided through command-line
initial begin 
   run_test ("base_test");
   // Or you can leave the argument as blank
   // run_test ();
end
   
// Command-line arguments for an EDA simulator
$> [simulator] -f list +UVM_TESTNAME=base_test

  

UVM Base Test Example

In the following example, a custom test called base_test that inherits from uvm_test is declared and registered with the factory.

Testbench environment component called m_top_env and its configuration object is created during the build_phase and setup according to the needs of the test. It is then placed into the configuration database using uvm_config_db so that other testbench components within this environment can access the object and configure sub components accordingly.

  
  
   // Step 1: Declare a new class that derives from "uvm_test"
   class base_test extends uvm_test;
   
   	  // Step 2: Register this class with UVM Factory
      `uvm_component_utils (base_test)
      
      // Step 3: Define the "new" function 
      function new (string name, uvm_component parent = null);
         super.new (name, parent);
      endfunction

      // Step 4: Declare other testbench components
      my_env   m_top_env;              // Testbench environment
      my_cfg   m_cfg0;                 // Configuration object
      

      // Step 5: Instantiate and build components declared above
      virtual function void build_phase (uvm_phase phase);
         super.build_phase (phase);

         // [Recommended] Instantiate components using "type_id::create()" method instead of new()
         m_top_env  = my_env::type_id::create ("m_top_env", this);
         m_cfg0     = my_cfg::type_id::create ("m_cfg0", this);
      
         // [Optional] Configure testbench components if required
         set_cfg_params ();

         // [Optional] Make the cfg object available to all components in environment/agent/etc
         uvm_config_db #(my_cfg) :: set (this, "m_top_env.my_agent", "m_cfg0", m_cfg0);
      endfunction

      // [Optional] Define testbench configuration parameters, if its applicable
      virtual function void set_cfg_params ();
         // Get DUT interface from top module into the cfg object
         if (! uvm_config_db #(virtual dut_if) :: get (this, "", "dut_if", m_cfg0.vif)) begin
            `uvm_error (get_type_name (), "DUT Interface not found !")
         end
         
         // Assign other parameters to the configuration object that has to be used in testbench
         m_cfg0.m_verbosity    = UVM_HIGH;
         m_cfg0.active         = UVM_ACTIVE;
      endfunction

	  // [Recommended] By this phase, the environment is all set up so its good to just print the topology for debug
      virtual function void end_of_elaboration_phase (uvm_phase phase);
         uvm_top.print_topology ();
      endfunction

      function void start_of_simulation_phase (uvm_phase phase);
         super.start_of_simulation_phase (phase);
         
         // [Optional] Assign a default sequence to be executed by the sequencer or look at the run_phase ...
         uvm_config_db#(uvm_object_wrapper)::set(this,"m_top_env.my_agent.m_seqr0.main_phase",
                                          "default_sequence", base_sequence::type_id::get());

      endfunction
      
      // or [Recommended] start a sequence for this particular test
      virtual task run_phase (uvm_phase phase);
      	my_seq m_seq = my_seq::type_id::create ("m_seq");
      	
      	super.run_phase(phase);
      	phase.raise_objection (this);
      	m_seq.start (m_env.seqr);
      	phase.drop_objection (this);
      endtask
   endclass 

  

The UVM topology task print_topology displays all instantiated components in the environment and helps in debug and to identify if any component got left out.

A test sequence object is built and started on the environment virtual sequencer using its start method.

Derivative Tests

A base test helps in the setup of all basic environment parameters and configurations that can be overridden by derivative tests. Since there is no definition for build_phase and other phases that are defined differently in dv_wr_rd_register, its object will inherently call its parent's build_phase and other phases because of inheritance. Function new is required in all cases and simulation will give a compilation error if its not found.

  
  
// Build a derivative test that launches a different sequence
// base_test <- dv_wr_rd_register_test
class dv_wr_rd_register_test extends base_test;
	`uvm_component_utils (dv_wr_rd_register_test)
	
	function new(string name = "dv_wr_rd_register_test");
		super.new(name);
	endfunction
	
	// Start a different sequence for this test
	virtual task run_phase(uvm_phase phase);
		wr_rd_reg_seq 	m_wr_rd_reg_seq = wr_rd_reg_seq::type_id::create("m_wr_rd_reg_seq");
		
		super.run_phase(phase);
		phase.raise_objection(this);
		m_wr_rd_reg_seq.start(m_env.seqr);
		phase.drop_objection(this);
	endtask
endclass

  

In this case, only run_phase will be overriden with new definition in derived test and its super call will invoke the run_phase of the base_test.

Assume that we now want to run the same sequence as in dv_wr_rd_register_test but instead want this test to be run on a different configuration of the environment. In this case, we can have another derivative test of the previous class and define its build_phase in a different way.

  
  
// Build a derivative test that builds a different configuration
// base_test <- dv_wr_rd_register_test <- dv_cfg1_wr_rd_register_test

class dv_cfg1_wr_rd_register_test extends dv_wr_rd_register_test;
	`uvm_component_utils (dv_cfg1_wr_rd_register_test)
	
	function new(string name = "dv_cfg1_wr_rd_register_test");
		super.new(name);
	endfunction
	
	// First calls base_test build_phase which sets m_cfg0.active to ACTIVE
	// and then here it reconfigures it to PASSIVE
	virtual function void build_phase(uvm_phase phase);
		super.build_phase(phase);
		m_cfg0.active = UVM_PASSIVE;
	endfunction
endclass