Why we should use non-blocking assignments in driver and blocking assignments in monitor?

Why do we use non-blocking assignments in driver and blocking assignments in monitor?

Nonblocking assignments in the driver help avoid race conditions in the underlying DUT. They allow the driver to change signal values “just after the clock” such as


task run_phase( uvm_phase phase );
  int top_idx = 0;
  
  // Default conditions:
  ADPCM.frame <= 0;
  ADPCM.data <= 0;
  forever begin
    @(posedge ADPCM.clk);
     ADPCM.frame <= 1; // Start of frame
    for(int i = 0; i < 8; i++) begin // Send nibbles
      @(posedge ADPCM.clk);
      ADPCM.data <= req.data[3:0];
      req.data = req.data >> 4;
    end
  
    ADPCM.frame <= 0; // End of frame
    seq_item_port.item_done(); // Indicates that the sequence_item has been consumed
  end
endtask: run

This allows the driver to interact with the DUT as if it were a “normal” SystemVerilog module. For monitors, on the other hand, we use blocking assignments because there is no worry about race conditions, since monitors typically sample values on the clock edge and then just do stuff with them:


task run_phase( uvm_phase phase );
    wb_txn txn, txn_clone;
    txn = wb_txn::type_id::create("txn");  // Create once and reuse
    forever @ (posedge m_v_wb_bus_if.clk)
      if(m_v_wb_bus_if.s_cyc) begin // Is there a valid wb cycle?
        txn.adr = m_v_wb_bus_if.s_addr; // get address
        txn.count = 1;  // set count to one read or write
  ...//You get the idea

Hope this helps.

In reply to ram007:
The general rule is if one process writes to a variable, and another process reads the same variable synchronized to the same event, you must use a non-blocking assignment to that variable to prevent race conditions.

The process that generates a clock is not synchronized to itself, so you should not be using non-blocking assignments. Reset depends if it is synchronous or asynchronous.

In reply to dave_59:
Dave,
I’m trying understand your comment. Below is my clock generation in my testbench.


  initial begin
    sys_clk <= 0;
    forever #(`CYCLE/2.0) sys_clk = ~sys_clk;
  end

The clock is being written in this process. Is it because the clock is not “read” but is used as a procedural time control that it doesn’t fall under your first sentence?

Thanks,
Mark

In reply to markylew:
You could say that.

Also, to be noted that we should not use non-blocking assignment on automatic variables, such as object. So what we do in a monitor is that we sample the interface value into a transaction which is automatic, so here we must use blocking assignment.

for a drive, we use non-blocking because it will eliminate the race condition by scheduling the updation of LHS in the NBA region.

In reply to voraravi:

Class objects do not have automatic lifetimes, and do not have the restriction on NBA assignments that automatic variables have.

In reply to dave_59:

Thank you for correction.

In reply to dave_59:

In reply to ram007:
The general rule is if one process writes to a variable, and another process reads the same variable synchronized to the same event, you must use a non-blocking assignment to that variable to prevent race conditions.
The process that generates a clock is not synchronized to itself, so you should not be using non-blocking assignments. Reset depends if it is synchronous or asynchronous.

Hi Dave, so to clarify, if I have something like this


module xyz_tb_top();

reg i_local;
reg i_flop;

 task abc(input reg i);
   i_local = i;
 endtask

 always @(posedge clk)
 begin
   i_flop <= i_local;
 end

endmodule

Should I use non blocking when assigning i_local to i to avoid race conditions? Because if I use blocking assignment, I see i_flop changing the same cycle as i_local. Is this the race condition you are talking about? Please let me know.

In reply to vk7715:

Yes, that is a race condition if the task abc gets called synchronized to the same clock event.

In reply to tfitz:

Is there a problem if we use non-blocking in the monitor code as well to sample the signals? I mean both the driver and monitor code have non-blocking assignment in their code ?

In reply to vk7715:

In reply to dave_59:
Hi Dave, so to clarify, if I have something like this


module xyz_tb_top();
reg i_local;
reg i_flop;
task abc(input reg i);
i_local = i;
endtask
always @(posedge clk)
begin
i_flop <= i_local;
end
endmodule

Should I use non blocking when assigning i_local to i to avoid race conditions? Because if I use blocking assignment, I see i_flop changing the same cycle as i_local. Is this the race condition you are talking about? Please let me know.

I frequently see similar code in UVM environment, a sequence use blocking assignment to set i_local, then pass to driver to assign with non-blocking assignment. Why it’s not a problem for UVM?

In reply to Elaine_71:

Realize that a UVM testbench is more like software passing values through task/function arguments. If you try to use a non-blocking assignment to a variable and then immediately pass that variable to a routine argument, the argument gets the old value of the variable. In the design, we do not use function calls to pass values; we set signals before a clock edge, and the design captures the value synchronized to a clock edge. We use non-blocking assignments to the DUT to prevent races conditions with the clock edge.

In reply to dave_59:
thanks for explanation