Unable to figure out fix for race condition in uvm driver: any help is very much appreciated

Hi everyone,

I’ve got a UVM driver that sends data to the DUT. Under some circumstances the DUT could request the stimulus to be halted if the RTL doesn’t have space available to store the next data beat.
In the UVM driver I’m finding a race condition. The driver sends data to the DUT, but when it’s about to send the data the value of the halt signal isn’t updated. This results in the RTL missing one of the data beats because it doesn’t have storage available for it.
I’ve tried using clocking blocks, but I haven’t been able to figure out why the driver doesn’t see the updated value of the halt signal.

I’ve pasted below the two “versions” of the UVM driver as well as the interface where it shows the clocking block.
Note that I’m not using any sequences and sequencer. I’m randomly generating data for the RTL in the driver.

Driver using clocking blocks:


class duplex_if_driver extends uvm_driver;
  `uvm_component_utils(duplex_if_driver)
    
  //Config object:
  duplex_if_cfg                                                  m_cfg;
  //Virtual interface:
  virtual                            duplex_if_if #(addr_width_p, data_width_p)      vif;

  duplex_if_txn#(addr_width_p, data_width_p) duplex_txn_q[$];
  

  bit       drive_reads;
  rand int unsigned delay;
  bit       disable_delay_constraint;
  rand bit [data_width_p-1:0] data;
  rand bit error;
  bit       disable_error_constraint;
  bit previous_clk_finish;
  int unsigned  clk_counter;
  int unsigned read_delay_q[$];
  int unsigned dummy_halt;
  
  
  constraint random_delay{
                          if(!disable_delay_constraint) {
    delay dist{
               [1:5] :/ 80,
               [6:20] :/ 10,
               [21:200]:/ 5,
               [201:500]:/ 5
               };
  }
                          }
  constraint random_error { //Most of the time the interface won't return an error
                            if(!disable_error_constraint) {
    error dist {0 :/98, 1 :/ 2 };
  }
                            }
  
  function new(string name, uvm_component parent=null);
    super.new(name, parent);
  endfunction
  
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
  endfunction
  
  function void connect_phase(uvm_phase phase);
    super.connect_phase(phase);
  endfunction

  task run_phase(uvm_phase phase);
    super.run_phase(phase);
    
    if(drive_reads)
      reads();
    else
      writes();
    
  endtask // run_phase

  task reads();
    bit active_read;
    int unsigned previous_q_size;
    
    forever begin
	 @(vif.cb_write);
	   
       vif.cb_write.finish  <= '0;
       vif.cb_write.error   <= 'X;
       vif.cb_write.data    <= 'X;
       vif.cb_write.b2b_rready <= 0;

       dummy_halt = vif.cb_write.b2b_halt;
      
      
      if(vif.reset_in==0) begin
       	previous_q_size = read_delay_q.size;


        if(read_delay_q.size>0) begin
          
          delay = read_delay_q[0];
          
          if(delay==0 && vif.cb_write.b2b_halt==0) begin //when the simulator reaches here the value of this signal isn't updated yet
            disable_delay_constraint  = 1;
            disable_error_constraint  = 0;
            active_read               = 1'b0;
            
            if(!this.randomize()) 
              `uvm_fatal({"duplex_if_driver_",get_name()}, "Randomization Failure")

             vif.cb_write.finish                <= 1'b1;
             vif.cb_write.error                 <= error;
             vif.cb_write.data                  <= data;

            read_delay_q.pop_front;         
            
            vif.cb_write.b2b_rready <= 1;

          end
          
            for(int i=0; i < read_delay_q.size ; i++) 
              if(read_delay_q[i]>0)
                read_delay_q[i]--;
        end
        
  
	if(vif.cb_write.enable && (read_delay_q.size < mem_b2b_reads_p)) begin
          disable_delay_constraint    = 0;
          disable_error_constraint    = 1;
          
          if(!this.randomize()) 
            `uvm_fatal({"duplex_if_driver_",get_name()}, "Randomization Failure")

//          active_read             = 1'b1;

          read_delay_q.push_back(delay);
        end
        
        previous_clk_finish           = vif.cb_write.finish;

      end // if (vif.reset_in         ==0)

      if (previous_q_size < mem_b2b_reads_p)  vif.cb_write.b2b_rready <= 1;       
      clk_counter++;

    end // forever begin    
  endtask // reads
  
endclass

Interface where the clocking blocks are defined:


interface duplex_if_if          #(int addr_width_p=32,
                                  int data_width_p=16
                                  ) (
                                     input                           clk_in,
                                     input                           reset_in
                                     );
  
  wire                                                               clk;
  wire                                                               reset;

  assign clk    = clk_in;
  assign reset  = reset_in;
  
  logic [addr_width_p-1:0]                                           addr;
  logic                                                              enable;
  logic                                                              finish;
  logic                                                              b2b_rready;
  logic                                                              b2b_halt;
  logic                                                              error;
  logic [data_width_p-1:0]                                           data;


 
  
  clocking cb_write @(posedge clk);
    default input #1step output #1step; // Sample signals (input) 1step before posedge, drive signals (output) 1step after posedge
    output                                                           error, finish , b2b_rready, data;
    input                                                            addr, enable, b2b_halt;
  endclocking // cb_write
  
endinterface


Driver reading signal values straight from the interface:


class duplex_if_driver extends uvm_driver;
  `uvm_component_utils(duplex_if_driver)
    
  //Config object:
  duplex_if_cfg                                                  m_cfg;
  //Virtual interface:
  virtual                            duplex_if_if #(addr_width_p, data_width_p)      vif;

  duplex_if_txn#(addr_width_p, data_width_p) duplex_txn_q[$];
  

  bit       drive_reads;
  rand int unsigned delay;
  bit       disable_delay_constraint;
  rand bit [data_width_p-1:0] data;
  rand bit error;
  bit       disable_error_constraint;
  bit previous_clk_finish;
  int unsigned  clk_counter;
  int unsigned read_delay_q[$];
  int unsigned dummy_halt;
  
  
  constraint random_delay{
                          if(!disable_delay_constraint) {
    delay dist{
               [1:5] :/ 80,
               [6:20] :/ 10,
               [21:200]:/ 5,
               [201:500]:/ 5
               };
  }
                          }
  constraint random_error { //Most of the time the interface won't return an error
                            if(!disable_error_constraint) {
    error dist {0 :/98, 1 :/ 2 };
  }
                            }
  
  function new(string name, uvm_component parent=null);
    super.new(name, parent);
  endfunction
  
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
  endfunction
  
  function void connect_phase(uvm_phase phase);
    super.connect_phase(phase);
  endfunction

  task run_phase(uvm_phase phase);
    super.run_phase(phase);
    
    if(drive_reads)
      reads();
    else
      writes();
    
  endtask // run_phase

//non clocking	
	task reads();
    bit active_read;
    int unsigned previous_q_size;
    
    forever begin
      @(posedge vif.clk_in);

      vif.finish  = '0;
      vif.error   = 'X;
      vif.data    = 'X;
      vif.b2b_rready = 0;
      dummy_halt = vif.b2b_halt;
        
      if(vif.reset_in==0) begin
        previous_q_size = read_delay_q.size;

        if(read_delay_q.size>0) begin
          
          delay = read_delay_q[0];
          
          if(delay==0 && vif.b2b_halt==0) begin //when the simulator reaches here the value of this signal isn't updated yet
            disable_delay_constraint  = 1;
            disable_error_constraint  = 0;
            active_read               = 1'b0;
            
            if(!this.randomize()) 
              `uvm_fatal({"duplex_if_driver_",get_name()}, "Randomization Failure")
            
            vif.finish                <= 1'b1;
            vif.error                 <= error;
            vif.data                  <= data;


            read_delay_q.pop_front;         
            vif.b2b_rready <= 1;

          end
          
            for(int i=0; i < read_delay_q.size ; i++) 
              if(read_delay_q[i]>0)
                read_delay_q[i]--;
        end
        
        if(vif.enable && (read_delay_q.size < mem_b2b_reads_p)) begin
          disable_delay_constraint    = 0;
          disable_error_constraint    = 1;
          
          if(!this.randomize()) 
            `uvm_fatal({"duplex_if_driver_",get_name()}, "Randomization Failure")

//          active_read             = 1'b1;
          read_delay_q.push_back(delay);
        end
        previous_clk_finish           = vif.finish;
        
      end // if (vif.reset_in         ==0)

      if (previous_q_size < mem_b2b_reads_p)  vif.b2b_rready <= 1;       
      
      clk_counter++;
    end // forever begin    
  endtask // reads

	
	
  
endclass

Nasty fix without using clocking blocks that I really don’t like:


      @(posedge vif.clk_in);
        #1;

I’ve run the test using breakpoints and I see that by the time the UVM driver reaches the b2b_halt signal its value isn’t updated yet. Is there any way I can ensure that by the time the simulation reaches the check for the ‘halt’ signal the value corresponds to what the DUT it’s outputting?

Many thanks in advance, any help is very much appreciated :)
Antonio

In reply to antonio92m:

You are not consequent using the clocking block. The reset signal belongs also to the clockvars, b ut you are excluding it from the cb.
What is the reason to use wires for clock and reset in the interface?

In reply to chr_sue:

Hi chr_sue,
I will include the reset in the clocking block and use logic instead of wires and retry.
What’s the advantage of using logic instead of wires here? - I’m not very familiar with clocking blocks.

Thanks a lot,
Antonio

In reply to antonio92m:

wire is the nte type, but there is need for it, because there are only variables.

In reply to chr_sue:

I’ve added now the reset signal to the input of the clocking block but the race condition still remains. By the time that the driver executes:


if(delay==0 && vif.cb_write.b2b_halt==0) begin //when the simulator reaches here the value of this signal isn't updated yet

the value of the signal isn’t updated yet.

In reply to antonio92m:

Do you mean the interface signal

vif.cb_write.b2b_halt

or the

delay

.

In reply to chr_sue:

The value of the signal ‘vif.cb_write.b2b_halt’ . The variable delay is updated correctly.

In reply to antonio92m:

Is this an output of the DUT or does it come from the testbench?

In reply to chr_sue:

It’s a DUT output. And when the DUT asserts the ‘halt’ signal the driver never reads it “in time”.

In reply to antonio92m:

Did you check it comes correctly out of the DUT?

In reply to chr_sue:

Yes, the signal comes out correctly from the DUT. I’m guessing this happens due to the SV scheduler.
I’ve checked the waves and also set breakpoints in the code.
I also see the DUT generates the signal correctly because when I don’t use the clocking blocks and set a small fix delay after the rising edge of the clock by the time the execution of the code reaches:


if(delay==0 && vif.cb_write.b2b_halt==0) begin //when the simulator reaches here the value of this signal isn't updated yet

the driver sees the correct value in the ‘halt’ signal.

In reply to antonio92m:

It is now guessing what I do, but you can sample this signal on the negedge in the clocking block:
simply include in the clocking block:

input negedge b2b_halt

In reply to chr_sue:

So, the DUT tells the UVM driver to stop sending data by asserting the ‘halt’ signal. The RTL sets this signal to ‘1’ when it doesn’t have more storage available.
Let’s say that the previous cycle the RTL received data and that has filled the data buffer in the RTL. This would mean that the current cycle the RTL would set the ‘halt’ signal to ‘1’.
The driver sends data to the RTL in the rising edge of the clock. If the driver doesn’t read the value of the ‘halt’ signal until the negedge of the clock this would mean that the current cycle the driver would send data to the RTL even when the ‘halt’ signal was asserted.

I did try using negedge as well in the clocking block, but it doesn’t fix the problem.

In reply to antonio92m:

Sorry, but I see only a piece of your problem and cannot give you a helpful advice.
But I do not believe this is a race condition. I believe this is a protocol timing issue.

In reply to chr_sue:

I really appreciate your help.
What do you mean by a protocol timing issue? My assumption is that if the RTL asserts any kind of ‘ready’ or ‘not_ready’ signal in a certain cycle the stimuli components should be able to detect it in the same cycle and not a cycle later.
In my case, the ‘halt’ signal behaves like a ‘not_ready’ signal and I was expecting the driver to detect this in time. In fact, when I set the fixed delay (#1 when I don’t use clocking blocks) works.

In reply to antonio92m:

Yes I understand. What you could do is to replace the #1step in your clocking block with an appropriate time which refers to your clock frequency. #1step is the smallest delay which can be used. Mabe there is something wrong with using blocking and non-blocking assigments.