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.