What is a driver ?

UVM driver is an active entity that has knowledge on how to drive signals to a particular interface of the design. For example, in order to drive a bus protocol like APB, UVM driver defines how the signals should be timed so that the target protocol becomes valid. All driver classes should be extended from uvm_driver, either directly or indirectly.

Transaction level objects are obtained from the Sequencer and the UVM driver drives them to the design via an interface handle.

Class Hierarchy

uml_uvm_driver_class_hier

Steps to create a UVM driver

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

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

  	// [Recommended] Makes this driver more re-usable
  	`uvm_component_utils (my_driver)

  	// This is standard code for all components
  	function new (string name = "my_driver", uvm_component parent = null);
    	super.new (name, parent);
  	endfunction
  	
  	// Code for rest of the steps come here
endclass
2. Declare virtual interface handle and get them in build phase

  	// Actual interface object is later obtained by doing a get() call on uvm_config_db
  	virtual if_name vif;
  
  	virtual function void build_phase (uvm_phase phase);
  		super.build_phase (phase);
     	if (! uvm_config_db #(virtual if_name) :: get (this, "", "vif", vif)) begin
        	`uvm_fatal (get_type_name (), "Didn't get handle to virtual interface if_name")
     	end
	endfunction
3. Code the run_phase


	// This is the main piece of driver code which decides how it has to translate
	// transaction level objects into pin wiggles at the DUT interface
	virtual task run_phase (uvm_phase phase);
		// Loop the following steps
		// 1. Get next item from the sequencer
		// 2. Assign data from the received item into DUT interface
		// 3. Finish driving transaction
	endtask
uvm_driver-env

UVM Driver-Sequencer handshake

UVM driver is a parameterized class which can drive a specific type of transaction object. The driver has a TLM port of type uvm_seq_item_pull_port which can accept the parameterized request object from the uvm_sequencer. It can also provide a response object back to the sequencer and usually the class type of both request and response items are the same. However, they can be different if explicitly specified.

The UVM driver uses the following methods to interact with the sequencer.

Method NameDescription
get_next_itemBlocks until a request item is available from the sequencer. This should be followed by item_done call to complete the handshake.
try_next_itemNon-blocking method which will return null if a request object is not available from the sequencer. Else it returns a pointer to the object.
item_doneNon-blocking method which completes the driver-sequencer handshake. This should be called after get_next_item or a successful try_next_item call.

How are driver/sequencer API methods used ?

The idea behind a driver/sequencer handshake mechanism is to allow the driver to get a series of transaction objects from the sequence and respond back to the sequence after it has finished driving the given item so that it can get the next item.

1.get_next_item followed by item_done

This use model allows the driver to get an object from the sequence, drive the item and then finish the handshake with the sequence by calling item_done(). This is the preferred use model since the driver need to operate only when the sequencer has an object for the driver. Here, finish_item call in the sequence finishes only after the driver returns item_done call.


	virtual task run_phase (uvm_phase phase);
		my_data req_item;
		
		forever begin
			// 1. Get next item from the sequencer
			seq_item_port.get_next_item (req_item);
			
			// 2. Drive signals to the interface
			@(posedge vif.clk);
			vif.en <= 1;
			// Drive remaining signals, put write data/get read data
			
			// 3. Tell the sequence that driver has finished current item
			seq_item_port.item_done();
		end
2.get followed by put

The difference between this model and the previous one is that here, the driver gets the next item and sends back the sequence handshake in one go, before the UVM driver processes the item. Later on the driver uses the put method to indicate that the item has been finished. So, finish_item call in the sequence is finished as soon as get() is done.


	virtual task run_phase (uvm_phase phase);
		my_data req_item;
		
		forever begin
			// 1. finish_item in sequence is unblocked
			seq_item_port.get (req_item);
			
			// 2. Drive signals to the interface
			@(posedge vif.clk);
			vif.en = 1;
			// Drive remaining signals
			
			// 3. Finish item
			seq_item_port.put (rsp_item);
		end
	endtask

UVM Driver Example


class my_driver extends uvm_driver #(my_data);
  `uvm_component_utils (my_driver)
  
   virtual  dut_if   vif;
      
   function new (string name, uvm_component parent);
      super.new (name, parent);
   endfunction

   virtual function void build_phase (uvm_phase phase);
      super.build_phase (phase);
      if (! uvm_config_db #(virtual dut_if) :: get (this, "", "vif", vif)) begin
         `uvm_fatal (get_type_name (), "Didn't get handle to virtual interface dut_if")
      end
   endfunction
 
   task run_phase (uvm_phase phase);
      my_data data_obj;
      super.run_phase (phase);
      
      forever begin
         `uvm_info (get_type_name (), $sformatf ("Waiting for data from sequencer"), UVM_MEDIUM)
         seq_item_port.get_next_item (data_obj);
         drive_item (data_obj);
         seq_item_port.item_done ();
      end
   endtask
   
   virtual task drive_item (my_data data_obj);
      // Drive based on bus protocol
   endtask
endclass

Note the following from example shown above :

  • Driver is extended from uvm_driver
  • A virtual interface handle vif is declared and assigned later in the build_phase().
  • Real interface object is retrieved from the database directly into a local variable using uvm_config_db::get() method
  • Get the next data item from sequencer using seq_item_port.get_next_item() in run_phase
  • Call drive task to send data in accordance with a bus protocol
  • Indicate to the sequencer that the data item has been driven using seq_item_port.item_done()


Other Details

The base driver class contains a uvm_seq_item_pull_port through which it can request for new transactions from the export connected to it. The ports are typically connected to the exports of a sequencer component in the parent class where the two are instantiated.The RSP port needs connecting only if the driver will use it to write responses to the analysis export in the sequencer.


class uvm_driver #(type REQ = uvm_sequence_item, type RSP = REQ) extends uvm_component;

The driver's port and the sequencer's export are connected during the connect_phase() of an environment/agent class.


virtual function void connect_phase ();
   m_drv0.seq_item_port.connect (m_seqr0.seq_item_export);
endfunction