What is a UVM agent ?

An agent encapsulates a Sequencer, Driver and Monitor into a single entity by instantiating and connecting the components together via TLM interfaces. Since UVM is all about configurability, an agent can also have configuration options like the type of UVM agent (active/passive), knobs to turn on features such as functional coverage, and other similar parameters.

Types of Agents

Active
  • Instantiates all three components [Sequencer, Driver, Monitor]
  • Enables data to be driven to DUT via driver
Passive
  • Only instantiate the monitor
  • Used for checking and coverage only
  • Useful when there's no data item to be driven to DUT

How to find out if a UVM agent is active or passive ?

User-defined agent classes derived from uvm_agent also have another function called get_is_active() which will return the state of the requested UVM agent.


	// Assume this is inside the user-defined agent class
	if (get_is_active()) begin
		// Build driver and sequencer
	end
	// Build monitor

Class Hierarchy

uml_uvm_monitor_class_hier

Steps to create a UVM agent

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

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

	// Create handles to all agent components like driver, monitor and sequencer
	// my_driver, my_monitor and agent_cfg are custom classes assumed to be defined
	// Agents can be configured via a configuration object that can be passed in from the test
	my_driver                  m_drv0;
    my_monitor                 m_mon0;
    uvm_sequencer #(my_data)   m_seqr0;
    agent_cfg                  m_agt_cfg;
3. Instantiate and build components

      virtual function void build_phase (uvm_phase phase);
		 
		 // If this UVM agent is active, then build driver, and sequencer
         if (get_is_active()) begin
            m_seqr0 = uvm_sequencer#(my_data)::type_id::create ("m_seqr0", this);
            m_drv0 = my_driver::type_id::create ("m_drv0", this);
         end
         
         // Both active and passive agents need a monitor
         m_mon0 = my_monitor::type_id::create ("m_mon0", this);
         
         //[Optional] Get any agent configuration objects from uvm_config_db
      endfunction
4. Connect agent components together

      virtual function void connect_phase (uvm_phase phase);
      
		 // Connect the driver to the sequencer if this agent is Active
         if (get_is_active())
            m_drv0.seq_item_port.connect (m_seqr0.seq_item_export);
      endfunction

What does a UVM agent do ?

Usually, it makes sense to create an agent that provides protocol specific tasks to generate transactions, check the results and perform coverage. For example, a UVM agent can be created for the WishBone protocol whose sequencer will generate data items which can be sent to the driver. The driver then converts the data item class object into actual pin level signals and drive them to the DUT. The monitor may passively collect the outputs from the DUT, convert them back into another data item class object and distribute it among all the components in the testbench waiting for the item.

How to configure a UVM agent as active or passive ?

A user-defined agent derived from uvm_agent has an internal variable called is_active which is of enum type uvm_active_passive_enum. By default, all agents are active.


	// Set the configuration called "is_active" to the agent's path to mark the given agent as passive
	uvm_config_db #(int) :: set (this, "path_to_agent", "is_active", UVM_PASSIVE);
	
	// Set the configuration called "is_active" to the agent's path to mark the given agent as active
	uvm_config_db #(int) :: set (this, "path_to_agent", "is_active", UVM_ACTIVE);

Example of a UVM Agent

Each agent should have a configuration object which will contain a reference to the virtual interface that can be used by its driver and monitor to access pin level signals. This object can also contain other data members which will control which of the agents sub-components are built and it may also contain information that affects the behavior of the agents components. Some of the other functionalities that can be included are :

  • Functional coverage monitor to collect protocol information
  • Scoreboard if required to check protocol data
  • API sequences that might be useful for implementing an API layer


   class my_agent extends uvm_agent;
      `uvm_component_utils (my_agent)

      my_driver                  m_drv0;
      my_monitor                 m_mon0;
      uvm_sequencer #(my_data)   m_seqr0;
      agent_cfg                  m_agt_cfg;
      
      function new (string name = "my_agent", uvm_component parent=null);
         super.new (name, parent);
      endfunction

      // If Agent is Active, create Driver and Sequencer, else skip
      // Always create Monitor regardless of Agent's nature
      
      virtual function void build_phase (uvm_phase phase);
         super.build_phase (phase);
         
		 uvm_config_db #(agent_cfg) :: get (this, "*", "agt_cfg", m_agt_cfg);         
		 
         if (get_is_active()) begin
            m_seqr0 = uvm_sequencer#(my_data)::type_id::create ("m_seqr0", this);
            m_drv0 = my_driver::type_id::create ("m_drv0", this);
            m_drv0.vif = m_agt_cfg.vif;
         end
         m_mon0 = my_monitor::type_id::create ("m_mon0", this);
         
         m_mon0.vif = m_agt_cfg.vif;
      endfunction

	  // Connect Sequencer to Driver, if the agent is active
	  
      virtual function void connect_phase (uvm_phase phase);
         if (get_is_active())
            m_drv0.seq_item_port.connect (m_seqr0.seq_item_export);
      endfunction
   endclass

Note the following:

  • An agent should be derived from the uvm class uvm_agent
  • Agent being a component, is registered with the factory using macro `uvm_component_utils
  • Instantiations m_seqr0, m_drv0, m_mon0 are sequencer, driver and monitor respectively
  • uvm_active_passive_enum is a UVM enum declaration that stores UVM_ACTIVE or UVM_PASSIVE. This is usually used to configure the agent to be either active/passive
  • In the build_phase(), sequencer and driver are created only if the agent is configured to be active. The variable is_active can be set either at environment level or via a configuration object retrieved from resource database.
  • Similarly, connection between sequencer and driver needs to be done only if they were built during the build phase.

Recommended Practice

It is recommended to derive your own agents from the base class uvm_agent for the following reasons :

  • Allows you to distinguish agents from other component types also using its inheritance
  • Any future changes and additions to the base UVM agent class will be automatically included in your derived class
  • Has get_is_active() method which will return the state of an agent.