Using wait(valid) in monitor get double transaction

Hello,

I’m building a basic UVM driver-monitor transaction flow.
The sequencer send a transaction to dut every clock cycle. the interface hold a req.val bit. Sequencer randomly drive val=0 or val=1
The monitor use wait(val) to only generate transaction when this cycle see a “valid” input on interface.
The issue is, when there’s a val=0 cycle followed by a val=1 cycle, at the val=1 cycle(@30, @40), the monitor code execute twice(@50). No duplicate when consecutive val=1 (@160, @170, @180)
if I replace wait(val) with if(val) however, this issue goes away.

Could anyone explain why this is happening, what’s wrong with using wait()?


//sequencer
  ...
  task body();
    // get interface
    if(!uvm_config_db #(virtual alu_if)::get(p_sequencer, "", "alu_if", vif)) begin
      uvm_report_fatal("", "");
    end
    repeat(30) begin // control test length. use for loop?
      @(vif.mon_cb) begin
        `uvm_create(req) // req is default parameer of type tr
        if($urandom_range(5,0) < 2) begin       
          req.randomize();
          req.val = 1;
          start_item(req);
          finish_item(req);
        end
        else begin
          req.randomize();
          req.val = 0;
          start_item(req);
          finish_item(req);         
        end
  ...
// driver
class alu_drv extends uvm_driver #(alu_op_tr);
  ...
  task run_phase(uvm_phase phase);
    super.run_phase(phase);

    forever begin
      if(!vif.drv_cb.reset) begin
        // req is template typed internal
        seq_item_port.get_next_item(req);  //blocking
        if(req) begin
          vif.drv_cb.val <= req.val;
          vif.drv_cb.Opcode <= req.Opcode;
          vif.drv_cb.SrcA <= req.SrcA;
          vif.drv_cb.SrcB <= req.SrcB;
        end
        seq_item_port.item_done();
      end
      @(vif.drv_cb);

    end
    
  endtask
  
endclass: alu_drv

// Monitor
class alu_monitor extends uvm_monitor;
  ...
  task run_phase(uvm_phase phase);
    // get intf
    if(!uvm_config_db #(virtual alu_if)::get(this,"","alu_if", intf)) begin
      uvm_report_fatal("", "");
    end
    
    forever begin
      alu_op_tr tr;
      @(intf.mon_cb);
      tr=new(); // temp var
      wait(intf.mon_cb.val && !intf.mon_cb.reset); // trans twice!!!
      //if(intf.mon_cb.val && !intf.mon_cb.reset) 
      begin
        tr.val = intf.mon_cb.val;
        tr.Opcode = intf.mon_cb.Opcode;
        tr.SrcA = intf.mon_cb.SrcA;
        mon_cnt=mon_cnt+1;
        `uvm_info("alu_mon:", $sformatf("[TIME===%4t] monitor alu: [cnt %4d] val:%d Opc:0x%x SrcA:0x%x",  $time, mon_cnt, tr.val, tr.Opcode, tr.SrcA), UVM_LOW)
        alu_op_ap.write(tr);
      end
    end
    
  endtask
  
endclass

// interface
interface alu_if (
  input CCLK,
  input reset,
  
  input val,
  input [3:0] Opcode,
  input [7:0] SrcA,
  input [7:0] SrcB,
  
  input [15:0] Result,
  input [1:0] ResEn
);
  
  clocking drv_cb @(posedge CCLK);
    input CCLK;
    input reset;
    
    inout val;
    inout Opcode;
    inout SrcA;
    inout SrcB;
  endclocking
  
  clocking mon_cb @(posedge CCLK);
    input CCLK;
    input reset;
    
    input val;
    input Opcode;
    input SrcA;
    input SrcB;
  
    input Result;
    input ResEn;
  endclocking
 
endinterface 

// top
module tb_top();
  logic clk;
  logic reset;
  int of;
  int cycle;
  logic [7:0] SrcA;
  logic [7:0] SrcB;
  logic [3:0] Opcode;
 
  // dut   
  alu dut(.CCLK(clk), .reset(reset));
  
  // interface
  alu_if intf (.CCLK(clk), .reset(reset), .val(dut.val), .Opcode(dut.Opcode), .SrcA(dut.SrcA),
                        .SrcB(dut.SrcB), .Result(dut.Result), .ResEn(dut.ResEn)
                       );
  ...

// log


UVM_INFO @ 0: reporter [RNTST] Running test alu_test...
UVM_INFO uvm_tb.sv(274) @ 0: uvm_test_top.alu_sb [alu_sb:] [TIME===   0] process model:  size:  0
UVM_INFO uvm_tb.sv(274) @ 0: uvm_test_top.alu_sb [alu_sb:] [TIME===   0] process model:  size:  0
<cycle:   1>
UVM_INFO uvm_tb.sv(274) @ 10: uvm_test_top.alu_sb [alu_sb:] [TIME===  10] process model:  size:  0
<cycle:   2>
UVM_INFO uvm_tb.sv(274) @ 20: uvm_test_top.alu_sb [alu_sb:] [TIME===  20] process model:  size:  0
<cycle:   3>
UVM_INFO uvm_tb.sv(274) @ 30: uvm_test_top.alu_sb [alu_sb:] [TIME===  30] process model:  size:  0
UVM_INFO uvm_tb.sv(80) @ 30: uvm_test_top.stim_env.alu_op_sqr@@op_seq [alu_seq:] [TIME===  30] seq generated: val:0 Opc:0x9 SrcA:0x73 SrbB:0x1d
<cycle:   4>
UVM_INFO uvm_tb.sv(274) @ 40: uvm_test_top.alu_sb [alu_sb:] [TIME===  40] process model:  size:  0
UVM_INFO uvm_tb.sv(80) @ 40: uvm_test_top.stim_env.alu_op_sqr@@op_seq [alu_seq:] [TIME===  40] seq generated: val:1 Opc:0x6 SrcA:0x49 SrbB:0xdc
<cycle:   5>
UVM_INFO uvm_tb.sv(207) @ 50: uvm_test_top.chk_env.alu_mon [alu_mon:] [TIME===  50] monitor alu: [cnt    1] val:1 Opc:0x6 SrcA:0x49
UVM_INFO uvm_tb.sv(263) @ 50: uvm_test_top.alu_sb [alu_sb:] [TIME===  50] alu_sb_ap_export: alu_op_tr recived, entry size:  1. Opcode:0x6 SrcA:0x49
UVM_INFO uvm_tb.sv(207) @ 50: uvm_test_top.chk_env.alu_mon [alu_mon:] [TIME===  50] monitor alu: [cnt    2] val:1 Opc:0x6 SrcA:0x49
UVM_INFO uvm_tb.sv(263) @ 50: uvm_test_top.alu_sb [alu_sb:] [TIME===  50] alu_sb_ap_export: alu_op_tr recived, entry size:  2. Opcode:0x6 SrcA:0x49
UVM_INFO uvm_tb.sv(274) @ 50: uvm_test_top.alu_sb [alu_sb:] [TIME===  50] process model:  size:  2
UVM_INFO uvm_tb.sv(80) @ 50: uvm_test_top.stim_env.alu_op_sqr@@op_seq [alu_seq:] [TIME===  50] seq generated: val:0 Opc:0xc SrcA:0xff SrbB:0xbe
<cycle:   6>
UVM_INFO uvm_tb.sv(274) @ 60: uvm_test_top.alu_sb [alu_sb:] [TIME===  60] process model:  size:  2
UVM_INFO uvm_tb.sv(80) @ 60: uvm_test_top.stim_env.alu_op_sqr@@op_seq [alu_seq:] [TIME===  60] seq generated: val:1 Opc:0x0 SrcA:0x48 SrbB:0x86
<cycle:   7>
UVM_INFO uvm_tb.sv(207) @ 70: uvm_test_top.chk_env.alu_mon [alu_mon:] [TIME===  70] monitor alu: [cnt    3] val:1 Opc:0x0 SrcA:0x48
UVM_INFO uvm_tb.sv(263) @ 70: uvm_test_top.alu_sb [alu_sb:] [TIME===  70] alu_sb_ap_export: alu_op_tr recived, entry size:  3. Opcode:0x0 SrcA:0x48
UVM_INFO uvm_tb.sv(207) @ 70: uvm_test_top.chk_env.alu_mon [alu_mon:] [TIME===  70] monitor alu: [cnt    4] val:1 Opc:0x0 SrcA:0x48
UVM_INFO uvm_tb.sv(263) @ 70: uvm_test_top.alu_sb [alu_sb:] [TIME===  70] alu_sb_ap_export: alu_op_tr recived, entry size:  4. Opcode:0x0 SrcA:0x48
UVM_INFO uvm_tb.sv(274) @ 70: uvm_test_top.alu_sb [alu_sb:] [TIME===  70] process model:  size:  4
UVM_INFO uvm_tb.sv(80) @ 70: uvm_test_top.stim_env.alu_op_sqr@@op_seq [alu_seq:] [TIME===  70] seq generated: val:0 Opc:0x9 SrcA:0xca SrbB:0x8c
...
<cycle:  16>
UVM_INFO uvm_tb.sv(207) @ 160: uvm_test_top.chk_env.alu_mon [alu_mon:] [TIME=== 160] monitor alu: [cnt    7] val:1 Opc:0xd SrcA:0x48
UVM_INFO uvm_tb.sv(263) @ 160: uvm_test_top.alu_sb [alu_sb:] [TIME=== 160] alu_sb_ap_export: alu_op_tr recived, entry size:  7. Opcode:0xd SrcA:0x48
UVM_INFO uvm_tb.sv(207) @ 160: uvm_test_top.chk_env.alu_mon [alu_mon:] [TIME=== 160] monitor alu: [cnt    8] val:1 Opc:0xd SrcA:0x48
UVM_INFO uvm_tb.sv(263) @ 160: uvm_test_top.alu_sb [alu_sb:] [TIME=== 160] alu_sb_ap_export: alu_op_tr recived, entry size:  8. Opcode:0xd SrcA:0x48
UVM_INFO uvm_tb.sv(274) @ 160: uvm_test_top.alu_sb [alu_sb:] [TIME=== 160] process model:  size:  8
UVM_INFO uvm_tb.sv(80) @ 160: uvm_test_top.stim_env.alu_op_sqr@@op_seq [alu_seq:] [TIME=== 160] seq generated: val:1 Opc:0xb SrcA:0xfa SrbB:0x8f
<cycle:  17>
UVM_INFO uvm_tb.sv(207) @ 170: uvm_test_top.chk_env.alu_mon [alu_mon:] [TIME=== 170] monitor alu: [cnt    9] val:1 Opc:0xb SrcA:0xfa
UVM_INFO uvm_tb.sv(263) @ 170: uvm_test_top.alu_sb [alu_sb:] [TIME=== 170] alu_sb_ap_export: alu_op_tr recived, entry size:  9. Opcode:0xb SrcA:0xfa
UVM_INFO uvm_tb.sv(274) @ 170: uvm_test_top.alu_sb [alu_sb:] [TIME=== 170] process model:  size:  9
UVM_INFO uvm_tb.sv(80) @ 170: uvm_test_top.stim_env.alu_op_sqr@@op_seq [alu_seq:] [TIME=== 170] seq generated: val:1 Opc:0xd SrcA:0xb3 SrbB:0x24
<cycle:  18>
UVM_INFO uvm_tb.sv(207) @ 180: uvm_test_top.chk_env.alu_mon [alu_mon:] [TIME=== 180] monitor alu: [cnt   10] val:1 Opc:0xd SrcA:0xb3
UVM_INFO uvm_tb.sv(263) @ 180: uvm_test_top.alu_sb [alu_sb:] [TIME=== 180] alu_sb_ap_export: alu_op_tr recived, entry size: 10. Opcode:0xd SrcA:0xb3
UVM_INFO uvm_tb.sv(274) @ 180: uvm_test_top.alu_sb [alu_sb:] [TIME=== 180] process model:  size: 10
UVM_INFO uvm_tb.sv(80) @ 180: uvm_test_top.stim_env.alu_op_sqr@@op_seq [alu_seq:] [TIME=== 180] seq generated: val:0 Opc:0x5 SrcA:0xcf SrbB:0x0c

In reply to Elaine_71:

Several issues:
You are using the virtual interface in the sequence. Your sequence should never access or use any parts of the vif. It is solely to generate untimed transactions. Remove the vif from your sequence and the @(vif.mon_cb).

In your monitor, you want to use the clocking block to synchronize signal values.


// Monitor
class alu_monitor extends uvm_monitor;
  ...
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    // get intf
    if(!uvm_config_db #(virtual alu_if)::get(this,"","alu_if", intf)) begin
      uvm_report_fatal("", "");
    end
  endfunction
    
  task run_phase(uvm_phase phase);
    alu_op_tr tr;
    forever begin
      while ((intf.mon_cb.val != 1) || (intf.mon_cb.reset == 1)) @(intf.mon_cb);
      tr = alu_op_tr::type_id::create("tr")); // temp var
      tr.val = intf.mon_cb.val;
      tr.Opcode = intf.mon_cb.Opcode;
      tr.SrcA = intf.mon_cb.SrcA;
      mon_cnt=mon_cnt+1;
      `uvm_info("alu_mon:", $sformatf("[TIME===%4t] monitor alu: [cnt %4d] val:%d Opc:0x%x SrcA:0x%x",  $time, mon_cnt, tr.val, tr.Opcode, tr.SrcA), UVM_LOW)
      alu_op_ap.write(tr);
    end
  endtask
 
endclass

In reply to cgales:
thanks for pointing out the improper use vif in sequence.
A follow up question. If I want to ask sequence to generate 1 item each cycle rather than pre-generate a bunch at beginning, what can I do?

In reply to cgales:
Back to the original question, why using vif in sequence cause double transaction seen on monitor when using wait()?

In reply to Elaine_71:

It’s difficult to provide an exact answer without being able to reproduce the issue, but there are different timing semantics when using @(intf.mon_cb) and wait(intf.mon_cb.val). The wait statement is blocking, so there may be several clock cycles pass until this unblocks. After the loop finishes, the @(intf.mon_cb) may occur in the same time slice, so it enters the same loop again and you see the same transaction get sampled.

The general rule for clocking blocks is that you use @(intf.mon_cb) to ensure that all signals are updated at that point, and then determine action based on the signal values. You don’t want to use any other blocking constructs when using clocking blocks.

In reply to cgales:

You are getting “double transactions” because val is asserted in two successive cycles, so these look like two back-to-back transactions. How are two transactions separated? When val goes to 0 and then back to 1? Then you need to add a wait (val==0); at the end of your loop.