Hi,
The UVM cookbook on page 266 states following regarding the pipelined accesses:
" Pipelined Accesses
Pipelined accesses are primarily used to stress test the bus but they require a different approach in the sequence. A
pipelined sequence needs to have a seperate threads for generating the request sequence items and for handling the
response sequence items.
The generation loop will block on each finish_item() call until one of the threads in the driver completes a get() call.
Once the generation loop is unblocked it needs to generate a new item to have something for the next driver thread to
get(). Note that a new request sequence item needs to be generated on each iteration of the loop, if only one request item
handle is used then the driver will be attempting to execute its contents whilst the sequence is changing it.
Since the driver uses pipelined accesses, the sequence could modify a request item while the driver is still working on the previous sequence item. If only one handle is used for request item, driver could be executing its contents and sequence could be simultanesously modifying it. However, the code for pipelined sequence given below(reproduced from UVM cookbook) uses only one handle :
class mbus_pipelined_seq extends uvm_sequence #(mbus_seq_item);
`uvm_object_utils(mbus_pipelined_seq)
logic[31:0] addr[10]; // To save addresses
int count; // To ensure that the sequence does not complete too early
function new(string name = "mbus_pipelined_seq");
super.new(name);
endfunction
task body;
mbus_seq_item req = mbus_seq_item::type_id::create("req");
use_response_handler(1);
count = 0;
for(int i=0; i<10; i++) begin
start_item(req);
assert(req.randomize() with {MREAD == 0; MOPCODE == SINGLE; MADDR inside {[32'h0010_0000:32'h001F_FFFC]};});
addr[i] = req.MADDR;
finish_item(req);
end
foreach (addr[i]) begin
start_item(req);
req.MADDR = addr[i];
req.MREAD = 1;
finish_item(req);
end
// Do not end the sequence until the last req item is complete
wait(count == 20);
endtask: body
// This response_handler function is enabled to keep the sequence response
// FIFO empty
function void response_handler(uvm_sequence_item response);
count++;
endfunction: response_handler
endclass: mbus_pipelined_seq
Here the create function is not put inside the loop but is outside loop. So only one request item will be generated and there will be only one handle for request item. Is this an error in the code given in the UVM cookbook or my understanding is not correct?. Can someone explain please
In this case, there only needs to be one sequence_item handle. After creating the sequence_item handle, the sequence will configure the transaction. The transaction is passed to the driver by the finish_item() call. The driver will process the request’s command phase, and copy all the required information to the response. When the command phase is completed, the finish_item() returns back to the sequence which can use the handle for the next transaction.
Hi Cgales,
thanks for the reply. I have a question here-
The driver uses get and put_response(req) protocol. Since get() internally calls item_done, finish_item() call will unblock, completing the current loop iteration. so the item will be randomized again. Since the driver is pipelined and is not copying the request information to response information, the request item will be modified when the data phase of the transaction is still executing. Will this cause a problem? Data phase of previous transaction will be executed with different values set by next iteration. Is this understanding correct?
I am giving the driver code below for your reference:
//This class implements a pipelined driver
class mbus_pipelined_driver extends uvm_driver #(mbus_seq_item);
`uvm_component_utils(mbus_pipelined_driver)
virtual mbus_if MBUS;
function new(string name = "mbus_pipelined_driver", uvm_component parent = null);
super.new(name, parent);
endfunction
// The two pipeline processes use a semaphore to ensure orderly execution
semaphore pipeline_lock = new(1);
//
// The run_phase(uvm_phase phase);
//
// This spawns two parallel transfer threads, only one of
// which can be active during the cmd phase, so implementing
// the pipeline
//
task run_phase(uvm_phase phase);
@(posedge MBUS.MRESETN);
@(posedge MBUS.MCLK);
fork
do_pipelined_transfer;
do_pipelined_transfer;
join
endtask
//
Driver/Pipelined 246
UVM/OVM Documentation - Copyright (c) 2012 Mentor Graphics Corporation - http://verificationacademy.com/uvm-ovm
// This task is spawned in separate threads
//
task do_pipelined_transfer;
mbus_seq_item req;
forever begin
// Unblocks when the semaphore is available:
pipeline_lock.get();
seq_item_port.get(req);
accept_tr(req, $time);
void'(begin_tr(req, "pipelined_driver"));
MBUS.MADDR <= req.MADDR;
MBUS.MREAD <= req.MREAD;
MBUS.MOPCODE <= req.MOPCODE;
@(posedge MBUS.MCLK);
while(!MBUS.MRDY == 1) begin
@(posedge MBUS.MCLK);
end
// End of command phase:
// - unlock pipeline semaphore
pipeline_lock.put();
// Complete the data phase
if(req.MREAD == 1) begin
@(posedge MBUS.MCLK);
while(MBUS.MRDY != 1) begin
@(posedge MBUS.MCLK);
end
req.MRESP = MBUS.MRESP;
req.MRDATA = MBUS.MRDATA;
end
else begin
MBUS.MWDATA <= req.MWDATA;
@(posedge MBUS.MCLK);
while(MBUS.MRDY != 1) begin
@(posedge MBUS.MCLK);
end
req.MRESP = MBUS.MRESP;
end
// Return the request as a response
seq_item_port.put(req);
end_tr(req);
end
endtask: do_pipelined_transfer