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.


class my_top_env extends uvm_env;
   `uvm_component_utils (my_env)
   
   agent_apb          m_apb_agt;
   agent_wishbone     m_wb_agt;
   
   env_register       m_reg_env;
   env_analog         m_analog_env [2];
   
   scoreboard         m_scbd;
   
   function new (string name = "my_env", uvm_component parent);
      super.new (name, parent);
   endfunction
   
   virtual function void build_phase (uvm_phase phase);
      super.build_phase (phase);
      // Instantiate different agents and environments here
      m_apb_agt = agent_apb::type_id::create ("m_apb_agt", this);
      m_wb_agt  = agent_wishbone::type_id::create ("m_wb_agt", this);
      
      m_reg_env = env_register::type_id::create ("m_reg_env", this);
      foreach (m_analog_env[i]) 
        m_analog_env[i] = env_analog::type_id::create ($sformatf("m_analog_env%0d",m_analog_env[i]), this);
      
      m_scbd = scoreboard::type_id::create ("m_scbd", this);
   endfunction
   
   virtual function void connect_phase (uvm_phase phase);
   	   // Connect between different environments, agents, analysis ports, and scoreboard here
   endfunction
endclass

Note that env_analog or env_register environments can have other nested environments and agents within it. You can see how powerful UVM becomes in terms of reusability because of this hierarchical structure and TLM interfaces within each component.

Let's look at a simple testbench structure which instantiates a single agent and a scoreboard, as shown in the block diagram below.

uvm-env-tb2

The testbench structure translates to the following code.


class my_env extends uvm_env ;
   
   `uvm_component_utils (my_env)

   my_agent             m_agnt0;
   my_scoreboard        m_scbd0;
   
   function new (string name, uvm_component parent);
      super.new (name, parent);
   endfunction : new

   virtual function void build_phase (uvm_phase phase);
      super.build_phase (phase);
      m_agnt0 = my_agent::type_id::create ("my_agent", this);
      m_scbd0 = my_scoreboard::type_id::create ("my_scoreboard", this);
   endfunction : build_phase

   virtual function void connect_phase (uvm_phase phase);
      // Connect the scoreboard with the agent
      m_agnt0.m_mon0.item_collected_port.connect (m_scbd0.data_export); 
   endfunction

endclass

Note that the analysis port of the monitor is connected to the export of the scoreboard in connect_phase() method.

Environment Reuse Example

Lets assume that a DMA controller in an SoC has already been separately verified as a stand alone unit with its own verification environment. When the DMA controller design is used in multiple SoCs in different configurations, the verification environment can also be used in different system level testbenches with multiple configurations.

uvm_env_reuse