UVM pipelined driver cookbook

I am going through the UVM code on the pipelined driver from cookbook and i dont understand how command phase and data phase execute in parallel? m_bfm.begin_transfer is a blocking call and doesnt not return until the command phase and data phase of the same transaction. The assumption is once end_tr is called the current transaction is done and driver will request for next transaction.



forever begin
    seq_item_port.get(req);
    accept_tr(req, $time);
    void'(begin_tr(req, "pipelined_driver"));'

    // This blocking call performs the cmd phase of the request and then returns
    // right away before completing the data phase, thus allowing the cmd phase of 
    // the subsequent request (next loop iteration) to occur in parallel with the 
    // data phase of the current request, and so implementing the pipeline
    m_bfm.begin_transfer(req);
    pipeline.push_back(req);
  end
endtask: do_pipelined_transfers

// Function to complete the sequence item - driver handshake back to the sequence 
// item, decoupled from the point of the originating request
function void end_transfer(mbus_seq_item req);
  mbus_seq_item rsp = pipeline.pop_front();
  rsp.copy(req);
  //seq_item_port.put(rsp); // End of req item
  //put_response is a function instead of task:
  seq_item_port.put_response(rsp); // End of req item
  end_tr(rsp);
endfunction: end_transfer


interface mbus_pipelined_driver_bfm (
  input  logic         MCLK,
  input  logic         MRESETN,
  output logic [31:0]  MADDR,
  output logic [31:0]  MWDATA,
  output logic         MREAD,
  output mbus_opcode_e MOPCODE,
  input  logic         MRDY,
  input  logic [31:0]  MRDATA,
  input  mbus_resp_e   MRESP
);

mbus_pipelined_driver proxy;

mbus_seq_item current_tr;

event do_data_phase;

task begin_transfer(mbus_seq_item req);
  command_phase(req);

  pipeline_lock_get(); // Start of data phase: grab semaphore

  current_tr = req;
  ->do_data_phase;
endtask: begin_transfer

always begin
  @do_data_phase;

  @(posedge MCLK);

  data_phase(current_tr);
  proxy.end_transfer(current_tr);

  pipeline_lock_put(); // End of data phase: release semaphore
end

task wait_for_reset();
  @(posedge MRESETN);
  @(posedge MCLK);
endtask

task command_phase(mbus_seq_item req);
  MADDR <= req.MADDR;
  MREAD <= req.MREAD;
  MOPCODE <= req.MOPCODE;
  @(posedge MCLK);
  while (!MRDY == 1) begin
    @(posedge MCLK);
  end
  if (req.MREAD == 0) begin
    MWDATA <= req.MWDATA;
  end
endtask: command_phase

task data_phase(mbus_seq_item req);
  while (MRDY != 1) begin
    @(posedge MCLK);
  end
  req.MRESP = MRESP;
  if (req.MREAD == 1) begin
    req.MRDATA = MRDATA;
  end
endtask: data_phase

bit pipeline_lock = 0;

task pipeline_lock_get();
  while (pipeline_lock) begin
    @(posedge MCLK);
  end
  pipeline_lock = 1;
endtask: pipeline_lock_get

function void pipeline_lock_put();
  pipeline_lock = 0;
endfunction: pipeline_lock_put

endinterface: mbus_pipelined_driver_bfm

In reply to rag123:

No, In above code “m_bfm.begin_transfer(req)” blocking only for command phase.


task begin_transfer(mbus_seq_item req);
  command_phase(req); // blocking only for command.

  pipeline_lock_get(); // directly come out as pipeline_lock = 0. So, non blocking
 
  current_tr = req; // request assignment non blocking.
  ->do_data_phase; // trigger event for data phase and its non blocking.
endtask: begin_transfer

In begin_transfer once ->do_data_phase event call and task will complete.
However, data_phase will work in background and begin_transfer can able to accept second req item.
And for second request, it can block for next data phase only.
So, we can say that this is one stage pipeline.

Thanks,
Harsh

In reply to harsh pandya:

The next req gets processed only when the driver signals seq_item_port.put(rsp). That triggers the request for second req. I am not sure how the driver starts the second req as soon as the begin_transfer gets completed.

In reply to rag123:
Your understanding is correct in case when we are using get_next_item inside driver.
It is blocked until item_done or put(rsp).
But, get is non blocking. So, it is possible you get new seq_item.
Please, see below link.
https://verificationacademy.com/forums/uvm/difference-between-getnextitem-and-get#:~:text=get_next_item()%20is%20a%20blocking,of%20get()%2Fput()

Thanks,
Harsh

In reply to harsh pandya:

Thanks got it :) One more question, lets say if the above code is modified for 2 stage pipeline, lets say we have now command,data1, data2 phase. How would that look like?

In reply to rag123:

I have tried to modify original code for 2 stage. ( Please consider in terms of concept )
And if each phase is not depends on each other you can remove pipe_limit + pipe_lock_get/put.
I hope it helps.



task run_phase(uvm_phase phase);

fork
  data_1();
  data_2();
join_none

forever begin
    seq_item_port.get(req);
    .........
    .........
    m_bfm.begin_transfer(req);
    pipeline.push_back(req);
  end
endtask : run_phase

.........
.........
.........
int pipe_limite = 0;
mbus_seq_item data_1[$];
mbus_seq_item data_2[$];

task begin_transfer(mbus_seq_item req);
  while(pipe_limite == 2) @(posedge MCLK);
  command_phase(req);
endtask: begin_transfer

task command_phase(mbus_seq_item req);
........
........
pipe_limite ++;
data_1.push_back(req);
endtask: command_phase

task data_1();
  mbus_seq_item data_1_req;
  forever begin
    while(data_1.size() > 0) begin
      data_1_req = data_1.pop_front();
      ..........
      ..........
      pipeline_lock_get();
      data_2.push_back(data_1_req);  ​
   ​end
   ​@(posedge MCLK);
 ​end
endtask : data_1

task data_2();
 ​mbus_seq_item data_2_req;
 ​forever begin
   ​while(data_2.size() > 0) begin
     pipeline_lock_put(); // End of data phase: release semaphore
     data_2_req = data_2.pop_front();
     .........
     .........
     .........
     proxy.end_transfer(current_tr);
   ​end 
  ​@(posedge MCLK);
 ​end
endtask : data_2

function void pipeline_lock_put();
  pipe_limite -= 1;
  pipeline_lock = 0;
endfunction: pipeline_lock_put

Thanks,
Harsh

In reply to rag123:

I’m not sure if I understand your question right. But in a lot of situations you get more than 1 data back. Consider a burst read. You have a command phase indicating you have burst with 8 data getting back than you are reading according to your bus protocoöl the corresponding data from the bus.