In UVM, there is a mechanism to be followed when we want to send the transactions from the sequencer to the Driver in order to provide stimulus to the DUT. Since we know that the whole intelligence of different type of transactions is imbibed into the sequences and Sequencers are being used as the physical component to execute those Sequences.
A particular Sequence is directed to run on a Sequencer which in turns further breaks down into a series of transaction items and these transaction items are needs to be transferred to the Driver where these transaction items are converted into cycle based signal/pin level transitions.
Note: Transaction is a class of data members to be sent to the DUT, control knobs & constraints information. Transaction item is the object of type Transaction class.
Having said that, sending a transaction/sequence_item from a Sequencer to a Driver is a 4 step process which is listed below:
Sequencer side operation:
- Creating the “transaction item” with the declared handle using factory mechanism.
- Calling “start_item(<transaction_item_handle>)“. This call blocks the Sequencer till it grants the Sequence and transaction access to the Driver.
- Randomizing the transaction OR randomizing the transaction with in-line constraints. Now the transaction is ready to be used by the Driver.
- Calling “finish_item(<transaction_item_handle>)“. This call which is blocking in nature waits till Driver transfer the protocol related transaction data.
These are the operational steps from a Sequence which we want to execute using a Sequencer that is connected to a Driver inside an “Agent”. Whole of this process is shown in the Figure 1 & Figure 2 below:
Figure 1: Driver & Sequencer Interaction for Transaction Exchange
Figure 2: Transaction Execution Flow Between a Sequencer , Driver & Virtual Interface
If we talk about the actions taken by the Driver, then the following steps are made by the Driver in order to complete the communication with Sequencer(Sequence):
Driver side operation:
- Declaring the “transaction item” with a handle.
- Calling the “get_next_item(<transaction_item_handle>)“. Default transaction_handle is “req”. “get_next_item()” blocks the processing until the “req” transaction object is available in the sequencer request FIFO & later “get_next_item” returns with the pointer of the “req” object.
- Next, Driver completes its side protocol transfer while working with the virtual interface.
- Calling the “item_done()” OR “item_done(rsp)“. It indicates to the sequencer the completion of the process. “item_done” is a non-blocking call & can be processed with an argument or without an argument. If a response is expected by the Sequencer/Sequence then item_done(rsp) is called. It results in Sequencer response FIFO is updated with the “rsp” object handle.
The above mentioned handshaking behavior is shown by the written UVM code below:
Sequence Code:
////////// Sequence Declaration
class my_seq extends uvm_sequence #(my_txn);
`uvm_object_utils(my_seq)
// Constructor
function new (string name = “”);
super.new(name);
endfunction: new
// Body Task
task body;
repeat(n)
begin
my_txn txn;
// Step 1
start_item(txn);
// Step 2
txn = my_txn::type_id::create(“txn”, this);
// Step 3
assert(txn.randomize() with (cmd == 0) );
// Step 4
finish_item(txn);
end
endtask: body
endclass: my_seq
Following code is part of Driver code.
/////// Driver class
class my_driver extends uvm_driver #(my_txn);
`uvm_component_utils(my_driver)
………
………
// Run Task
task run_phase (uvm_phase phase);
// Step 1
my_txn txn;
forever begin
// Step 2
seq_item_port.get_next_item(txn);
// Step 3
@ (posedge vif.clk)
begin
vif.addr = txn.addr;
vif.data = txn.data;
vif.we = txn.we;
end
// Step 4
seq_item_port.item_done();
end
endtask: run_phase
………
endclass: my_driver
I hope it will provide the introductory idea about the concept. For further details, you may refer various available sources..Thank you for your time.