CFS Vision
How To Check if the DUT Is Stuck

How To Check if the DUT Is Stuck

In this article I am describing one technique (out of many) to catch the scenario in which a DUT (Device Under Test) gets stuck, no longer outputting any information on some particular bus.


A very common scoreboard architecture has the following elements:

  • an input port for receiving the expected information generated by the model
  • an input port for receiving the DUT outputted information
  • a queue in which to store the expected information (as the model is faster than the DUT)
  • some logic to compare the expected information from the model with the received information from the DUT

Most of the times, the “Compare Logic” is triggered when the agent connected on the outputs of the DUT collects the information generated by the DUT. So a typical code will look like this:

SystemVerilog
//Function for receiving information collected by the agent
//connected on the outputs of the DUT
virtual function void write_from_agent(cfs_acme_item item_dut);
  cfs_acme_item item_env = exp_items.pop_front();

  if(compare_items(item_dut, item_env) == 0) begin
    `uvm_error("DUT_ERROR", $sformatf(
      "Mismatch detected:%0n%0t-expected: %0s%0n%0t-received: %0s",
      item_env.convert2string(), item_dut.convert2string()))
  end 
  
endfunction


The problem with this code is that the check will only be triggered if the DUT outputs something!


If the DUT has a bug and its output interface remains idle then this check is not executed and we might even get a passing test.


There are multiple solutions to this problem. My default approach is to track how much time each entry from the model stayed in the scoreboard queue. If an entry stayed for too long then I trigger an error.

So the simple algorithm of this solution has four steps:

  1. When a new entry is pushed in the queue then start a timer associated with that particular entry.
  2. When the compare logic is triggered then stop the timer associated with the entry from the queue which took part in the compare logic.
  3. If any of the timers started goes beyond a threshold then trigger an error to show that the DUT got stuck.
  4. At reset terminate all timers.
Algorithm for starting and stopping the timers

Let’s see how we can implement this in the code!

Implementation

The first thing that we have to do is to declare all the class member fields required for this algorithm. Because we want to manipulate those timers from parallel threads we need a pointer to the SystemVerilog process of each timer. And because we want to associate each timer with an object pushed in the scoreboard’s queue we keep track of these process pointers using an associative array. The key for this associative array is the object pushed in the queue.

SystemVerilog
//Associative array containing pointers to the processes
//associated with each instance of the task timer()
protected process process_timer[cfs_acme_item];

Step #1: Start a timer when new entry comes from the model

To start a timer we just need to do two things:

  1. Start in the background a task called timer() (see Step #3 for its declaration).
  2. Save its process pointer in process_timer.
SystemVerilog
//Function for receiving information generated by the model
virtual function void write_from_model(cfs_acme_item item_env);
  exp_items.push_back(item_env);
  
  //Start the timer in the background via fork-join_none
  fork
    begin
      //Save the pointer of the process associated with the 
      //instance of the timer() task associated with item_env
      process_timer[item_env] = process::self();
      
      //Start the timer task
      timer(item_env);
    end 
  join_none
  
endfunction

The key thing to notice here is usage of the fork join_none construct which helps us create a separate process for the timer() task, save it in process_timer, and start the task in the background.

Step #2: Stop the timer when an entry is popped from the queue

Once we are at the point of comparing the expected item, from exp_items queue, with the one outputted by the DUT, we can also terminate the process of the timer() task associated with that particular expected item.

The code bellow presents one possibility of accomplishing this:

SystemVerilog
//Function for receiving information collected by the agent
//connected on the outputs of the DUT
virtual function void write_from_agent(cfs_acme_item item_dut);
  cfs_acme_item item_env = exp_items.pop_front();

  if(compare_items(item_dut, item_env) == 0) begin
    `uvm_error("DUT_ERROR", $sformatf(
      "Mismatch detected:%0n%0t-expected: %0s%0n%0t-received: %0s",
      item_env.convert2string(), item_dut.convert2string()))
  end 
  
  //Stop the timer by killing its associated process pointer
  process_timer[item_env].kill();
  
  //Remove the entry from the associative array
  process_timer.delete(item_env);
endfunction

The key thing to notice here is that this algorithm can easily work even if elements are popped from exp_items out of order!

Step #3: Trigger an error if a timer reached a threshold

The logic of the timer() task is pretty simple: we wait for a given number of clock cycles after which we trigger an error. We avoid triggering the error if the process associated with this instance of the timer() task is stopped by the logic from Step #2.

SystemVerilog
//Task for detecting that the DUT is stuck
protected virtual task timer(cfs_acme_item item_env);
  int unsigned threshold = 100;
  
  repeat(threshold) begin
    @(posedge vif.clk);
  end 
  
  //In a bug free DUT this error is blocked from triggering 
  //by the logic from Step #2 which kills this task
  `uvm_error("DUT_ERROR", 
    $sformatf("DUT got stuck and did not outputted item: %0s",
    item_env.convert2string()))
endtask

Because timer() task knows with which expected item it is associated, we can print this information in the error message, helping the design team track, through the RTL code, where the problem is.

The key thing to notice here is that we can make this task smarter and, for example, instead of blindly waiting the clock cycles (lines 5-7) we can have a counter which might pause counting while the module enters debug mode.

Step #4: Terminate all timers at reset

At reset we should simply kill all those timer() task instances. This is an easy job as we have the pointers of all those processes:

SystemVerilog
virtual function void handle_reset(string kind = "HARD");
  foreach(process_timer[item_env]) begin
    process_timer[item_env].kill();
    process_timer.delete(item_env);
  end 
endfunction

That’s it! We now have a flexible mechanism for detecting when a DUT becomes stuck.

Hope you found this useful!


If you want to gain an in-depth knowledge on how to do module level verification using SystemVerilog and UVM language then checkout my Udemy course called “Design Verification with SystemVerilog/UVM


Cristian Slav

Add comment