Welcome ! This website will help YOU (recent graduates/professionals) learn verification languages like SystemVerilog and UVM. Register for free and access more content !

What is a testcase ?

A testcase is a pattern to target checks and verify specific features of a design. Usually a verification plan is written that lists all the features and other functional items that needs to be verified, and then develop tests 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 change default sequences for each sequencer 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 driver 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 ();
 
         // [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
 
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 start a UVM test ?

The run_test() global task should be supplied with the name of the user-defined UVM test that needs to be started. If the argument to run_test() is left blank, then it is necessary to specify the testname via command-line options to the simulator as described in the next section.

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

How to start a different test than the one given in

run_test ?

This method is preferred because it allows more flexibility to choose different tests without modifying the Top Module SystemVerilog code every time you want to run a different test. Also, it avoids the need for recompilation.

If +UVM_TESTNAME is specified, then an object of the given test type is created by the factory and phases are started. 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 Test Example

Note the following from the example below:

  • base_test is extended from uvm_test and registered with factory using `uvm_component_utils
  • Testbench environment m_top_env and a configuration object m_cfg0 which holds all the tweak parameters are declared
  • During build_phase(), both the objects are created, config object is initialized with custom values and set as a variable in the database table of UVM using uvm_config_db::set ()
  • The config object is made available only to the agent my_agent inside m_top_env
  • Print the topology for debug purposes during end_of_elaboration_phase() - this phase is executed just before simluation and hence the entire hierarchy of class objects in the testbench will be built and visible by then
  • The sequencer inside my_agent is assigned a default sequence to execute upon start of simulation
 
   // 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.. 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");
        phase.raise_objection (this);
        m_seq.start (m_env.seqr);
        phase.drop_objection (this);
      endtask
   endclass 
 

Was this article helpful ?

We use cookies to personalize content and ads, to provide social media features and to analyze our traffic. You consent to our cookies if you continue to use our website. To find out more about the cookies we use and how to delete them, see our privacy policy.

  I accept cookies from this site.
Agree
EU Cookie Directive plugin by www.channeldigital.co.uk