A testcase is a pattern to target checks and verify specific features of a design. Usually we come up with a verification plan that lists the number of items and features required to be verified, and then develop tests to cover each of them.
So we'll end up writing quite a lot of different tests to reach and exercise every corner of the design. Instead of writing the same code for different tests, we put the entire testbench into a container called an Environment, and use the same environment with a different configuration for each test. Each test 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.
Now, let's see an example of how a test is written.
class base_test extends uvm_test; `uvm_component_utils (base_test) my_env m_top_env; // Testbench environment my_cfg m_cfg0; // Configuration object function new (string name, uvm_component parent = null); super.new (name, parent); endfunction virtual function void build_phase (uvm_phase phase); super.build_phase (phase); m_top_env = my_env::type_id::create ("m_top_env", this); m_cfg0 = my_cfg::type_id::create ("m_cfg0", this); // Configure the object set_cfg_params (); // Make the cfg object available to all components in agent uvm_config_db #(my_cfg) :: set (this, "m_top_env.my_agent", "m_cfg0", m_cfg0); endfunction 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 m_cfg0.m_verbosity = UVM_HIGH; m_cfg0.active = UVM_ACTIVE; endfunction virtual function void end_of_elaboration_phase (uvm_phase phase); // By now, the environment is all set up, just print the topology for debug uvm_top.print_topology (); endfunction function void start_of_simulation_phase (uvm_phase phase); super.start_of_simulation_phase (phase); // 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
Note the following from the example above:
- base_test is extended from
uvm_testand registered with factory using
- 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
- 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
This is the base class for all user-defined tests and provides the flexibility to select which test gets executed using
UVM_TESTNAME command-line argument. For any test to be executed during simulation, the
run_test() task should be called within an initial block in the top module. This task also accepts a testname which can be any of the user-defined test classes derived from
uvm_test. If the argument to
run_test() is left blank, then it is necessary to specify the testname in command-line as mentioned before.
// Specify the testname as an argument to the run_test () task initial run_test (base_test); ------------------------------------------------------------------------------- // Else, leave the argument to run_test as blank and specify via command-line initial run_test (); // At command prompt $> [simulator] -f list +UVM_TESTNAME=base_test
The second method is preferred because it allows more flexibility to choose different tests without modifying the Top Module system verilog code every time you want to run a different test. Also, it avoids the need for recompilation.
+UVM_TESTNAME is specified, then an object of type test_name is created by the factory and phasing begins. 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.
Refer to Class Definitions for more information regarding method declarations.