Why we should 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.