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.
The transfer of request and response sequence items between sequences and their target driver is facilitated by a TLM communication mechanism implemented in the sequencer.
The transfer of request and response sequence items between sequences and their target driver is facilitated by a TLM communication mechanism implemented in the sequencer.
Sending a sequence_item to a driver:
To send a sequence_item to a driver there are four steps that need to occur:
Step 1 - Creation
The sequence_item is derived from uvm_object and should be created via the factory:
Using the factory creation method allows the sequence_item to be overridden with a sequence_item of a derived type if required.
Step 2 - Ready - start_item()
The start_item() call is made, passing the sequence_item handle as an argument. This call blocks until the sequencer grants the sequence and the sequence_item access to the driver.
Step 3 - Set
The sequence_item is prepared for use, usually through randomization, but it may also be initialised by setting properties directly.
Step 4 - Go - finish_item()
The finish_item() call is made, which blocks until the driver has completed its side of the transfer protocol for the item. No simulation time should be consumed between start_item() and finish_item().
Step 5 - Response - get_response()
This step is optional, and is only used if the driver sends a response to indicate to indicate that it has completed transaction associated with the sequence_item. The get_response() call blocks until a response item is available from the sequencers response FIFO.
Step 1 - Creation
The sequence_item is derived from uvm_object and should be created via the factory:
Using the factory creation method allows the sequence_item to be overridden with a sequence_item of a derived type if required.
Step 2 - Ready - start_item()
The start_item() call is made, passing the sequence_item handle as an argument. This call blocks until the sequencer grants the sequence and the sequence_item access to the driver.
Step 3 - Set
The sequence_item is prepared for use, usually through randomization, but it may also be initialised by setting properties directly.
Step 4 - Go - finish_item()
The finish_item() call is made, which blocks until the driver has completed its side of the transfer protocol for the item. No simulation time should be consumed between start_item() and finish_item().
Step 5 - Response - get_response()
This step is optional, and is only used if the driver sends a response to indicate to indicate that it has completed transaction associated with the sequence_item. The get_response() call blocks until a response item is available from the sequencers response FIFO.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
my_sequence extends uvm_sequence #(my_sequence_item); | |
my_sequence_item item; | |
..... | |
task body(); | |
// Step 1 - Creation | |
item = my_sequence_item::type_id::create("item"); | |
// Step 2 - Ready - start_item() | |
start_item(item); | |
// Step 3 - Set | |
if(!item.randomize() with {address inside {[0:32'h4FFF_FFFF]};}) | |
begin | |
`uvm_error("body", "randomization failure for item") | |
end | |
// Step 4 - Go - finish_item() | |
finish_item(item); | |
endtask: body | |
endclass: my_sequence |
Late Randomization
In the sequence_item flow above, steps 2 and 3 could be done in any order. However, leaving the randomization of the sequence_item until just before the finish_item() method call has the potential to allow the sequence_item to be randomized according to conditions true at the time of generation. This is sometimes referred to as late randomization.
The alternative approach is to generate the sequence_item before the start_item() call, in this case the item is generated before it is necessarily clearhow it is going to be used.
The alternative approach is to generate the sequence_item before the start_item() call, in this case the item is generated before it is necessarily clearhow it is going to be used.
In previous generation verification methodologies, such as Specman and the AVM, generation was done at the beginning of the simulation and a stream of pre-prepared sequence_items was sent across to the driver. With late randomization, sequence_items are generated just in time and on demand.
Driver side Operation:
Multiple APIs used by driver code to interact with the sequencer. First go through these APIs,
get_next_item()
This method blocks until a REQ sequence_item is available in the sequencers request FIFO and then returns with a pointer to the REQ object.
The get_next_item() call implements half of the driver-sequencer protocol handshake, and it must be followed by an item_done() call which completes the handshake. Making another get_next_item() call before issuing an item_done() call will result in a protocol error and driver-sequencer deadlock.
try_next_item()
This is a non-blocking variant of the get_next_item() method. It will return a null pointer if there is no REQ sequence_item available in the sequencers request FIFO. However, if there is a REQ sequence_item available it will complete the first half of the driver-sequencer handshake and must be followed by an item_done() call to complete the handshake.
item_done()
The non-blocking item_done() method completes the driver-sequencer handshake and it should be called after a get_next_item() or a successful try_next_item() call.
This method blocks until a REQ sequence_item is available in the sequencers request FIFO and then returns with a pointer to the REQ object.
The get_next_item() call implements half of the driver-sequencer protocol handshake, and it must be followed by an item_done() call which completes the handshake. Making another get_next_item() call before issuing an item_done() call will result in a protocol error and driver-sequencer deadlock.
try_next_item()
This is a non-blocking variant of the get_next_item() method. It will return a null pointer if there is no REQ sequence_item available in the sequencers request FIFO. However, if there is a REQ sequence_item available it will complete the first half of the driver-sequencer handshake and must be followed by an item_done() call to complete the handshake.
item_done()
The non-blocking item_done() method completes the driver-sequencer handshake and it should be called after a get_next_item() or a successful try_next_item() call.
If it is passed no argument or a null pointer it will complete the handshake without placing anything in the sequencer's response FIFO. If it is passed a pointer to a RSP sequence_item as an argument, then that pointer will be placed in the sequencer's response FIFO.
The get_next_item() method initiate the sequencer arbitration process, which results in a sequence_item being returned from the active sequence which has selected. This means that the driver is effectively pulling sequence_items from the active sequences as it needs them.
When the handle to a sequence_item is passed as an argument to
the finish_item() method the drivers get_next_item() method call completes with a pointer to the same sequence_item. When the driver makes any changes to the sequence_item it is really updating the object inside the sequence. The drivers call to item_done() unblocks the finish_item() call in the sequence and then the sequence can access the fields in the
sequence_item, including those which the driver may have updated as part of the response side of the pin level transaction.
The get_next_item() method initiate the sequencer arbitration process, which results in a sequence_item being returned from the active sequence which has selected. This means that the driver is effectively pulling sequence_items from the active sequences as it needs them.
When the handle to a sequence_item is passed as an argument to
the finish_item() method the drivers get_next_item() method call completes with a pointer to the same sequence_item. When the driver makes any changes to the sequence_item it is really updating the object inside the sequence. The drivers call to item_done() unblocks the finish_item() call in the sequence and then the sequence can access the fields in the
sequence_item, including those which the driver may have updated as part of the response side of the pin level transaction.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Driver parameterised with the same sequence_item for request & response | |
// response defaults to request | |
class my_driver extends uvm_driver #(my_sequence_item); | |
my_interface vif; | |
..... | |
task run_phase(uvm_phase phase); | |
my_sequence_item req_item; | |
forever | |
begin | |
seq_item_port.get_next_item(req_item); // Blocking call returning the next transaction | |
@(posedge vif.clk); | |
vif.addr = req_item.address; // vif is the drivers Virtual Interface | |
// | |
// etc | |
// | |
// End of bus cycle | |
if(req_item.read_or_write == READ) | |
begin // Assign response data to the req_item fields | |
req_item.rdata = vif.rdata; | |
end | |
req_item.resp = vif.error; // Assign response to the req_item response field | |
seq_item_port.item_done(); // Signal to the sequence that the driver has finished with the item | |
end | |
endtask: run | |
endclass: my_driver | |
// Sequencer parameterised with the same sequence item for request & response | |
class my_sequencer extends uvm_sequencer #(my_sequence_item); | |
..... | |
endclass: my_sequencer | |
// Agent containing a driver and a sequencer | |
class my_agent extends uvm_agent; | |
my_driver m_driver; | |
my_sequencer m_sequencer; | |
my_interface vif; | |
my_agent_cfg m_cfg; | |
..... | |
// Sequencer-Driver connection: | |
function void connect_phase(uvm_phase phase); | |
if(m_cfg.active == UVM_ACTIVE) // The agent is actively driving stimulus | |
begin | |
m_driver.seq_item_port.connect(m_sequencer.seq_item_export); // TLM connection | |
m_driver.vif = vif; // Virtual interface assignment | |
end | |
endfunction: connect_phase | |
endclass: my_agent |
The connection between a driver and a sequencer is typically made in the connect_phase() method of an agent. With the standard UVM driver and sequencer base classes, the TLM connection between a driver and sequencer is a one to one connection - multiple drivers are not connected to a sequencer, nor are multiple sequencers connected to a driver.
uvm_sequence and uvm_driver have predefined sequence_item for both request and response.
So, you can directly use predefined variables for sequence_item,
uvm_sequence and uvm_driver have predefined sequence_item for both request and response.
So, you can directly use predefined variables for sequence_item,
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
virtual class uvm_sequence #(type REQ = uvm_sequence_item, | |
type RSP = REQ) extends uvm_sequence_base; | |
..... | |
REQ req; | |
RSP rsp; | |
..... | |
endclass: uvm_sequence | |
class uvm_driver #(type REQ=uvm_sequence_item, | |
type RSP=REQ) extends uvm_component; | |
..... | |
REQ req; | |
RSP rsp; | |
..... | |
endclass: uvm_driver | |
//Sequence, Sequencer and Driver parameterised with the different sequence_item for request & response | |
my_sequence extends uvm_sequence #(my_sequence_item1, (my_sequence_item2); // type of req = my_sequence_item1, type of rsp = my_sequence_item2 | |
..... | |
task body(); | |
// Step 1 - Creation | |
req = my_sequence_item1::type_id::create("req"); // type of req = my_sequence_item1 | |
// Step 2 - Ready - start_item() | |
start_item(req); | |
// Step 3 - Set | |
if(!req.randomize() with {address inside {[0:32'h4FFF_FFFF]};}) | |
begin | |
`uvm_error("body", "randomization failure for req") | |
end | |
// Step 4 - Go - finish_item() | |
finish_item(req); | |
// Step 5 - Response - get_response() | |
get_response(rsp); // type of rsp = my_sequence_item2 | |
endtask: body | |
endclass: my_sequence | |
class my_driver extends uvm_driver #(my_sequence_item); | |
my_interface vif; | |
..... | |
task run_phase(uvm_phase phase); | |
forever | |
begin | |
seq_item_port.get_next_item(req); // Blocking call returning the next transaction | |
@(posedge vif.clk); | |
vif.addr = req.address; // vif is the drivers Virtual Interface | |
// | |
// etc | |
// | |
// End of bus cycle | |
if(req.read_or_write == READ) | |
begin // Assign response data to the req fields | |
req.rdata = vif.rdata; | |
end | |
rsp = my_sequence_item2::type_id::create("rsp"); // type of rsp = my_sequence_item2 | |
req.resp = vif.error; // Assign response to the req_item response field | |
seq_item_port.item_done(rsp); // Signal to the sequence that the driver has finished with the item | |
end | |
endtask: run | |
endclass: my_driver | |
class my_sequencer extends uvm_sequencer #(my_sequence_item1, my_sequence_item2); | |
..... | |
endclass: my_sequencer | |
// Agent containing a driver and a sequencer | |
class my_agent extends uvm_agent; | |
my_driver m_driver; | |
my_sequencer m_sequencer; | |
my_interface vif; | |
my_agent_cfg m_cfg; | |
..... | |
// Sequencer-Driver connection: | |
function void connect_phase(uvm_phase phase); | |
if(m_cfg.active == UVM_ACTIVE) // The agent is actively driving stimulus | |
begin | |
m_driver.seq_item_port.connect(m_sequencer.seq_item_export); // TLM connection | |
m_driver.vif = vif; // Virtual interface assignment | |
end | |
endfunction: connect_phase | |
endclass: my_agent |
nice explaination
ReplyDeleteThanks!
DeleteFantastic explanation. I really appreciate your effort towards making it easier for people to learn UVM.
ReplyDeleteThank you,
Udit Khanna
Thanks!
DeleteWhat will happen if item_done is not sent back to the sequencer?
ReplyDeleteThe finish_item () method will keep blocking the driver and you will not be able to fetch the next transaction using get_next_item() method.
DeleteIn what condition, deadlock will occur in sequencer-driver?
ReplyDeleteWhat is actual meaning of deadlock?
I appreciate for this explanation. Thanks.
ReplyDelete