In the previous article, we saw how a UVM driver gets the next item by the calling get_next_item method, and how it informs the sequencer that the current item is done. Although this is the preferred way for driver-sequencer communications, UVM also gives us an alternative for a more complex implementation.

The other way is for the driver to use the get method to receive the next item and later use put to give a response item back to the sequencer.

driver-sequencer-get-and-put-flow

What is the driver code for get and put calls ?


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. Get an item from the sequencer using "get" method
      seq_item_port.get(req);

	  // 2. For simplicity, lets assume the driver drives the item and consumes 20ns of simulation time
      #20;

	  // 3. After the driver is done, assume it gets back a read data called 8'hAA from the DUT
	  // Assign the read data into the "request" sequence_item object
      req.data = 8'hAA;

	  // 4. Call the "put" method to send the request item back to the sequencer
      seq_item_port.put(req);
   endtask
endclass

How does the sequence start and stop an item ?


class my_sequence extends uvm_sequence #(my_data);
  `uvm_object_utils (my_sequence)

  // Create a sequence item object handle to store the sequence_item contents
  my_data tx;

  virtual task body();
  	// 1. Create the sequence item using standard factory calls
    tx = my_data::type_id::create("tx");
    
    // 2. Start this item on the current sequencer
    start_item(tx);
    
    // 3. Do late randomization since the class handle pointers are the same
    tx.randomize();
    
    // 4. Finish executing the item from the sequence perspective
    // The driver could still be actively driving and waiting for response
    finish_item(tx);
    
    // 5. Because "finish_item" does not indicate that the driver has finished driving the item,
    // the sequence has to wait until the driver explicitly tells the sequencer that the item is over
    // So, wait unitl the sequence gets a response back from the sequencer.
    get_response(tx);
    
    `uvm_info ("SEQ", $sformatf("get_response() fn call done rsp addr=0x%0h data=0x%0h, exit seq", tx.addr, tx.data), 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)

   virtual task run_phase(uvm_phase phase);
      super.run_phase(phase);
      
      // 1. Get sequence item from the sequencer using "get" method.
      // "req" is a pre-defined variable that comes with the class "uvm_driver"
      `uvm_info ("DRIVER", $sformatf ("Waiting for data from sequencer"), UVM_MEDIUM)
      seq_item_port.get(req);
      
      // 2. Assume that the driver drives this data during this time. For our purpose let's consider that the driver
      // consumes 20ns of simulation time
      `uvm_info ("DRIVER", $sformatf ("Start driving tx addr=0x%0h data=0x%0h", req.addr, req.data), UVM_MEDIUM)
      #20;
      
      // 3. Lets also assume that the DUT returned some read data back to the driver, which needs to be sent back
      // to the sequence. Note that read data is put into the same "request" class object handle 
      `uvm_info ("DRIVER", $sformatf ("#20 delay over, curr data=0x%0h", req.data), UVM_MEDIUM)
      req.data = 8'hAA;
      
      // 4. The driver calls the "put" method and sends back the response data to the sequencer
      `uvm_info ("DRIVER", $sformatf ("About to call put() with new data=0x%0h", req.data), UVM_MEDIUM)
      seq_item_port.put(req);
      `uvm_info ("DRIVER", $sformatf ("Finish driving tx addr=0x%0h data=0x%0h", req.addr, req.data), UVM_MEDIUM)
   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. Note that this sequence has an internal
  		// variable called "req" which can be directly used as well instead of "tx"
  		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, wait for rsp"), UVM_MEDIUM)
    	
    	// 5. Wait for the driver to finish driving the object, and respond back with a response object
    	// The transaction is said to be complete at this point. This task is blocking in nature and hence
    	// the sequence will end only after the driver returns a response item.
    	get_response(tx);
    	`uvm_info ("SEQ", $sformatf("get_response() fn call done rsp addr=0x%0h data=0x%0h, exit seq", tx.addr, tx.data), 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

Note that finish_item finished at time 0ns even before the driver started doing the pin wiggling (assumed to happen during the 20ns delay in this example). The get_response method is done at time 20ns when the driver sends back the response item.

It is also evident from the log that the data modified in the request packet within the driver can be obtained inside the sequence.

 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(76) @ 0: uvm_test_top.m_top_env.m_seqr0@@m_seq [SEQ] About to call start_item
UVM_INFO testbench.sv(78) @ 0: uvm_test_top.m_top_env.m_seqr0@@m_seq [SEQ] start_item() fn call done
UVM_INFO testbench.sv(80) @ 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(82) @ 0: uvm_test_top.m_top_env.m_seqr0@@m_seq [SEQ] finish_item() fn call done, wait for rsp
UVM_INFO testbench.sv(37) @ 20: uvm_test_top.m_top_env.m_drv0 [DRIVER] #20 delay over, curr data=0x1d
UVM_INFO testbench.sv(39) @ 20: uvm_test_top.m_top_env.m_drv0 [DRIVER] About to call put() with new data=0xaa
UVM_INFO testbench.sv(41) @ 20: uvm_test_top.m_top_env.m_drv0 [DRIVER] Finish driving tx addr=0x8f data=0xaa
UVM_INFO testbench.sv(84) @ 20: uvm_test_top.m_top_env.m_seqr0@@m_seq [SEQ] get_response() fn call done rsp addr=0x8f data=0xaa, exit seq
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 ---