uvm_driver is a child of uvm_component that has a TLM port to communicate with the sequencer. The driver is a parameterized class with the type of request and response sequence items. This allows the driver to send back a different sequence_item type back to the sequencer as the response. However, most drivers use a response object of the same type as the request sequence item.

The uvm_driver gets request sequence items (REQ) from the sequencer FIFO using a handshake mechanism and optionally returns a response sequence item (RSP) back to the sequencer response FIFO. There are primarily two ways for the driver to get a sequence item from the sequencer. In this article, we'll look at an example that uses the first style.

driver-sequencer-get-next-item-flow

Using get_next_item method in a driver

In this case, the driver requests for a sequence item from the sequencer using the get_next_item method through the seq_item_port TLM handle. Since the implementation of this port is defined in the sequencer, the function call makes the sequencer to pop an item from its internal FIFO and provide it to the driver via the argument provided in get_next_item method.


class my_driver extends uvm_driver #(my_data);
	`uvm_component_utils (my_driver)
	
   virtual task run_phase(uvm_phase phase);
      super.run_phase(phase);
      
      // 1. This task will get an item from the sequencer using get_next_item()
      `uvm_info ("DRIVER", $sformatf ("Waiting for data from sequencer"), UVM_MEDIUM)
      seq_item_port.get_next_item(req);
      
      // 2. For simplicity, lets just assume the driver drives the received packet
      // during this time and consumes 20ns to complete driving the transaction
      `uvm_info ("DRIVER", $sformatf ("Start driving tx addr=0x%0h data=0x%0h", req.addr, req.data), UVM_MEDIUM)
      #20;
      
      // 3. After driver has finished the transaction, it has to let the sequencer know
      // by calling item_done()
      `uvm_info ("DRIVER", $sformatf ("Finish driving tx addr=0x%0h data=0x%0h", req.addr, req.data), UVM_MEDIUM)
      seq_item_port.item_done();
   endtask

Once driver gets the next item, it can drive the data in the received sequence item to the DUT via a virtual interface handle. After the driver has finished driving the item, it has to let the sequencer know that the process has finished using item_done method.

How does the sequencer get these sequence items ?

A uvm_sequence is started on a sequencer which pushes the sequence item onto the sequencer's FIFO.


class my_sequence extends uvm_sequence;
	`uvm_object_utils (my_sequence)
	
	
  virtual task body();
  	// 1. Create an item the connected sequencer can accept
    my_data tx = my_data::type_id::create("tx");
    `uvm_info ("SEQ", $sformatf("About to call start_item"), UVM_MEDIUM)
    
    // 2. Call the start_item() task which will send this object to the driver
    start_item(tx);
    `uvm_info ("SEQ", $sformatf("start_item() fn call done"), UVM_MEDIUM)
    
    // 3. Because the class handle passed to the driver points to the same object, we 
    // can do late randomization
    tx.randomize();
    `uvm_info ("SEQ", $sformatf("tx randomized with addr=0x%0h data=0x%0h", tx.addr, tx.data), UVM_MEDIUM)
    
    // 4. Call finish_item method so that the sequence waits until the driver lets the 
    // sequencer know that this item has finished
    finish_item(tx);
    `uvm_info ("SEQ", $sformatf("finish_item() fn call done"), UVM_MEDIUM)
  endtask
endclass

Example

To illustrate how the get and put method calls between driver and sequencer work, let us build a simple testbench structure like the one shown below.

uvm-driver-sequencer-get-put-testbench

Define a sequence item

To keep things simple, let us define a transaction object class that will become the sequence item used for sequencer-driver communication. Assume that this sequence item contains two variables called addr and data.


// Note that this class is dervide from "uvm_sequence_item"
class my_data extends uvm_sequence_item;
  rand bit [7:0]   data;
  rand bit [7:0]   addr;

	// Rest of the class contents come here ...
endclass

Define the driver

The uvm_driver is parameterized to accept a class object of the type my_data and the driver is expected to unpack this class object and drive the signals appropriately to the DUT via the interface.


class my_driver extends uvm_driver #(my_data);
   `uvm_component_utils (my_driver)
   // Other parts of the driver code if they exist

   virtual task run_phase(uvm_phase phase);
      super.run_phase(phase);
      
      // 1. Get the next available item from the sequencer. If none exists, then wait until
      // next item is available -> this is blocking in nature
      `uvm_info ("DRIVER", $sformatf ("Waiting for data from sequencer"), UVM_MEDIUM)
      seq_item_port.get_next_item(req);
      
      // 2. Let us assume that the driver actively does the pin wiggling of the DUT during this time and 
      // consider it takes 20ns
      `uvm_info ("DRIVER", $sformatf ("Start driving tx addr=0x%0h data=0x%0h", req.addr, req.data), UVM_MEDIUM)
      #20;
      
      // 3. After the driver has driven all data to the DUT, it should let the sequencer know that it finished 
      // driving the transaction by calling "item_done". Optionally the response packet can be passed along with
      // the item_done method call and it will be placed in the sequencer's response FIFO
      `uvm_info ("DRIVER", $sformatf ("Finish driving tx addr=0x%0h data=0x%0h", req.addr, req.data), UVM_MEDIUM)
      seq_item_port.item_done();
   endtask
endclass

Define the sequence

A sequence item is always started using the start_item and finish_item methods.


class my_sequence extends uvm_sequence #(my_data);
  	// Rest of the sequence code

  	virtual task body();
  		// 1. Create a sequence item of the given type
    	my_data tx = my_data::type_id::create("tx");
    	`uvm_info ("SEQ", $sformatf("About to call start_item"), UVM_MEDIUM)
    	
    	// 2. Start the item on the sequencer which will send this to the driver
    	start_item(tx);
    	`uvm_info ("SEQ", $sformatf("start_item() fn call done"), UVM_MEDIUM)
    	
    	// 3. Do some late randomization to create a different content in this transaction object
    	tx.randomize();
    	`uvm_info ("SEQ", $sformatf("tx randomized with addr=0x%0h data=0x%0h", tx.addr, tx.data), UVM_MEDIUM)
    	
    	// 4. Call finish_item to let driver continue driving the transaction object or sequence item
    	finish_item(tx);
    	`uvm_info ("SEQ", $sformatf("finish_item() fn call done"), UVM_MEDIUM)
  	endtask
endclass

Define the test class

To keep things simple, let us directly create instances of the driver and sequencer inside the test class.

Driver and Sequencer should be instantiated inside an agent. An agent is instantiated in an environment and the the environment in turn should be created in the test.


class base_test extends uvm_test;
	// Rest of the test code is here

	// The sequencer is parameterized to accept objects of type "my_data" only
  	my_driver                	m_drv0;
  	uvm_sequencer #(my_data) 	m_seqr0;
  	my_sequence   				m_seq;

	// Build the sequencer and driver components
  	virtual function void build_phase(uvm_phase phase);
     	super.build_phase(phase);
     	m_drv0 = my_driver::type_id::create ("m_drv0", this);
     	m_seqr0 = uvm_sequencer#(my_data)::type_id::create ("m_seqr0", this);
  	endfunction
  
   	// Connect the sequencer "export" to the driver's "port"
   	virtual function void connect_phase (uvm_phase phase);
     	super.connect_phase (phase);
     	m_drv0.seq_item_port.connect (m_seqr0.seq_item_export);
   	endfunction

	// Start the sequence on the given sequencer
  	virtual task run_phase(uvm_phase phase);
    	m_seq = my_sequence::type_id::create("m_seq");
    	phase.raise_objection(this);
    	m_seq.start(m_seqr0);
    	phase.drop_objection(this);
  	endtask
endclass
 Simulation Log
UVM_INFO @ 0: reporter [RNTST] Running test base_test...
UVM_INFO testbench.sv(33) @ 0: uvm_test_top.m_top_env.m_drv0 [DRIVER] Waiting for data from sequencer
UVM_INFO testbench.sv(71) @ 0: uvm_test_top.m_top_env.m_seqr0@@m_seq [SEQ] About to call start_item
UVM_INFO testbench.sv(73) @ 0: uvm_test_top.m_top_env.m_seqr0@@m_seq [SEQ] start_item() fn call done
UVM_INFO testbench.sv(75) @ 0: uvm_test_top.m_top_env.m_seqr0@@m_seq [SEQ] tx randomized with addr=0x8f data=0x1d
UVM_INFO testbench.sv(35) @ 0: uvm_test_top.m_top_env.m_drv0 [DRIVER] Start driving tx addr=0x8f data=0x1d
UVM_INFO testbench.sv(37) @ 20: uvm_test_top.m_top_env.m_drv0 [DRIVER] Finish driving tx addr=0x8f data=0x1d
UVM_INFO testbench.sv(77) @ 20: uvm_test_top.m_top_env.m_seqr0@@m_seq [SEQ] finish_item() fn call done
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_objection.svh(1271) @ 20: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_report_server.svh(847) @ 20: reporter [UVM/REPORT/SERVER] 
--- UVM Report Summary ---