Funvizeo logo
  • Contents
      • Back
      • Verilog
      • SystemVerilog
      • UVM
      • Digital Basics
      • Verification
Most Popular
Verification
  Testbench Evolution
  Constraint Random Verification
  Verification Techniques
  Verification Plan
  Code Coverage

Verilog
  Data Types
  Basic Constructs
  Behavioral Modeling
  Gate Modeling
  Simulation Basics
  Design Examples
  Interview Questions

SystemVerilog
  Data Types
  Class
  Interface
  Constraints and more!
  Testbench Examples
  Interview Questions

UVM
  Sequences
  Testbench Components
  TLM Tutorial
  Register Model Tutorial
  Testbench Examples
  Interview Questions

Digital Fundamentals
  Binary Arithmetic
  Boolean Logic
  Karnaugh Maps
  Combinational Logic
  Sequential Logic

Configure Components

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.

Read more: Configure Components

UVM Testbench Top

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

Read more: UVM Testbench Top

UVM Test [uvm_test]

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

Class Hierarchy

uvm_test_inheritance

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

UVM Environment [uvm_env]

  1. What is UVM environment ?
  2. Why shouldn't verification components be placed directly in test class ?
  3. Class Hierarchy
  4. Steps to create a UVM environment
  5. UVM Environment Example

What is UVM environment ?

A UVM environment contains multiple, reusable verification components and defines their default configuration as required by the application. For example, a UVM environment may have multiple agents for different interfaces, a common scoreboard, a functional coverage collector, and additional checkers.

It may also contain other smaller environments that has been verified at block level and now integrated into a subsystem. This allows certain components and sequences used in block level verification to be reused in system level verification plan.

Why shouldn't verification components be placed directly in test class ?

It is technically possible to instantiate agents and scoreboards directly in a user defined uvm_test class.


class base_test extends uvm_test;
	`uvm_component_utils(base_test)
	
	apb_agent 			m_apb_agent;
	spi_agent 			m_spi_agent;
	
	base_scoreboard 	m_base_scbd;
	
	virtual function void build_phase(uvm_phase phase);
		// Instantiate agents and scoreboard
	endfunction
endclass

But, it is NOT recommended to do it this way because of the following drawbacks :

  • Tests are not reusable because they rely on a specific environment structure
  • Test writer would need to know how to configure the environment
  • Changes to the topology will require updating of multiple test files and take a lot of time

Hence, it is always recommended to build the testbench class from uvm_env, which can then be instantiated within multiple tests. This will allow changes in environment topology to be reflected in all the tests. Moreover, the environment should have knobs to configure, enable or disable different verification components for the desired task.

uvm_env-tb

uvm_env is the base class for hierarchical containers of other components that make up a complete environment. It can be reused as a sub-component in a larger environment or even as a stand-alone verification environment that can instantiated directly in various tests.

Class Hierarchy

uvm_env_class_hier

Steps to create a UVM environment

1. Create a custom class inherited from uvm_env, register with factory, and callnew

// my_env is user-given name for this class that has been derived from "uvm_env"
class my_env extends uvm_env;
 
    // [Recommended] Makes this driver more re-usable
    `uvm_component_utils (my_env)
 
    // This is standard code for all components
    function new (string name = "my_env", uvm_component parent = null);
      super.new (name, parent);
    endfunction
 
    // Code for rest of the steps come here
endclass
2. Declare and build verification components

	// apb_agnt and other components are assumed to be user-defined classes that already exist in TB
	apb_agnt	m_apb_agnt;
	func_cov 	m_func_cov;
	scbd 		m_scbd;
	env_cfg 	m_env_cfg;
	
	// Build components within the "build_phase"
	virtual function void build_phase (uvm_phase phase);
		super.build_phase (phase);
		m_apb_agnt = apb_agnt::type_id::create ("m_apb_agnt", this);
		m_func_cov = func_cov::type_id::create ("m_func_cov", this);
		m_scbd     = scbd::type_id::create ("m_scbd", this);
		
		// [Optional] Collect configuration objects from the test class if applicable
		if (uvm_config_db #(env_cfg)::get(this, "", "env_cfg", m_env_cfg))
			`uvm_fatal ("build_phase", "Did not get a configuration object for env")
			
		// [Optional] Pass other configuration objects to sub-components via uvm_config_db
	endfunction
3. Connect verification components together

	virtual function void connect_phase (uvm_phase phase);
		// A few examples:
		// Connect analysis ports from agent to the scoreboard
		// Connect functional coverage component analysis ports
		// ...
	endfunction

UVM Environment Example

This environment has 2 agents, 3 sub-environments and a scoreboard as represented in the block diagram shown above.

Read more: UVM Environment [uvm_env]

UVM Monitor [uvm_monitor]

What is a monitor ?

A UVM monitor is responsible for capturing signal activity from the design interface and translate it into transaction level data objects that can be sent to other components.

In order to do so, it requires the following:

  • A virtual interface handle to the actual interface that this monitor is trying to monitor
  • TLM Analysis Port declarations to broadcast captured data to others.

What does a UVM monitor do ?

A UVM monitor is derived from uvm_monitor base class and should have the following functions :

  1. Collect bus or signal information through a virtual interface
  2. Collected data can be used for protocol checking and coverage
  3. Collected data is exported via an analysis port

The UVM monitor functionality should be limited to basic monitoring that is always required.It may have knobs to enable/disable basic protocol checking and coverage collection. High level functional checking should be done outside the monitor, in a scoreboard.

uvm-monitor-env

Class Hierarchy

uvm-monitor-class-hier

Steps to create a UVM monitor

1. Create custom class inherited from uvm_monitor, register with factory and call new

// my_monitor is user-given name for this class that has been derived from "uvm_monitor"
class my_monitor extends uvm_monitor;

	// [Recommended] Makes this monitor more re-usable
	`uvm_component_utils (my_monitor)
	
	// This is standard code for all components
	function new (string name = "my_monitor", uvm_component parent = null);
		super.new (name, parent);
	endfunction
	
	// Rest of the steps come here
endclass
2. Declare analysis ports and virtual interface handles

	// Actual interface object is later obtained by doing a get() call on uvm_config_db
	virtual if_name vif;
	
	// my_data is a custom class object used to encapsulate signal information
	// and can be sent to other components
	uvm_analysis_port  #(my_data) mon_analysis_port;

Functions and tasks in a class are recommended to be declared as "virtual" to enable child classes to override them - Click to read on Polymorphism !

3. Build the UVM monitor

   virtual function void build_phase (uvm_phase phase);
      super.build_phase (phase);

      // Create an instance of the declared analysis port
      mon_analysis_port = new ("mon_analysis_port", this);
      
      // Get virtual interface handle from the configuration DB
      if (! uvm_config_db #(virtual if_name) :: get (this, "", "vif", vif)) begin
         `uvm_error (get_type_name (), "DUT interface not found")
      end
   endfunction	
4. Code the run_phase

	// This is the main piece of monitor code which decides how it has to decode 
	// signal information. For example, AXI monitors need to follow AXI protocol
	virtual task run_phase (uvm_phase phase);
		
	
		// Fork off multiple threads "if" required to monitor the interface,  for example:
		fork
			// Thread 1: Monitor address channel
			// Thread 2: Monitor data channel, populate "obj" data object
			// Thread 3: Monitor control channel, decide if transaction is over
			
			// Thread 4: When data transfer is complete, send captured information 
		 	// through the declared analysis port
			mon_analysis_port.write(obj);
		join_none
	endtask

UVM Monitor Example

Read more: UVM Monitor [uvm_monitor]

  1. UVM Sequencer [uvm_sequencer]
  2. UVM Driver [uvm_driver]
  3. UVM Agent | uvm_agent
  4. SystemVerilog fork join_none
  5. UVM TLM Sockets

Page 53 of 68

  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
Interview Questions
  Verilog Interview Set 1
  Verilog Interview Set 2
  Verilog Interview Set 3
  Verilog Interview Set 4
  Verilog Interview Set 5

  SystemVerilog Interview Set 1
  SystemVerilog Interview Set 2
  SystemVerilog Interview Set 3
  SystemVerilog Interview Set 4
  SystemVerilog Interview Set 5

  UVM Interview Set 1
  UVM Interview Set 2
  UVM Interview Set 3
  UVM Interview Set 4
Related Topics
  Digital Fundamentals
  Verilog Tutorial

  Verification
  SystemVerilog Tutorial
  UVM Tutorial
Latest in Verilog
  • Verilog $random
  • Verilog VCD Dump
  • Verilog VCD
  • Verilog Namespace
  • Verilog $stop $finish
Latest in SystemVerilog
  • SystemVerilog `define Macro
  • SystemVerilog Callback
  • SystemVerilog Interview Questions Set 10
  • SystemVerilog Interview Questions Set 9
  • SystemVerilog Interview Questions Set 8
Latest in UVM
  • UVM Callback
  • UVM Singleton Object
  • UVM Component [uvm_component]
  • UVM Object [uvm_object]
  • UVM Root [uvm_root]
© 2025 Funvizeo
Terms and Conditions