CFS Vision

Debugging Tip: Always Remember The Cause

It is a well known fact that debugging, not development, takes most of the time when verifying an DUT (Device Under Test). This is true for all software development branches, not just in hardware verification.
Beside the simulators, IDEs, machine clusters and any other thing that a company can invest in I believe that the biggest help in debugging must come from the guy developing the verification environment – and this one is free – debatable statement ๐Ÿ™‚

In my case, one thing that I am doing over and over again with a failing test is to double check that my verification environment signaled the DUT error correctly. In order to do this I am spending some (o lot of) time understanding:

  • what is the function for computing the expected output value – f(x, y)
  • what are the input parameters x and y

So in order to save some time I started coding my environment in such a way that I carry information like what is f(x, y), values of x and y, from conception all the way to the DUT output checker.



Let’s see this idea in a relevant example.

Let’s assume that we need to verify a DUT which does a simple job:
On a serial interfaces it gets bits of data. Once it gets a byte (MSB first) it pushes it into a FIFO which can be read from a parallel interface.

A typical waveform for such a DUT looks like the snapshot below.

Typical waveforms for a serial to parallel RTL
Typical waveforms for a serial to parallel RTL
Notice that the last outputted byte is wrong! Let’s see two ways of implementing the scoreboard and how small changes can impact the debugging time in a significant way.

The Bad Way

From the serial interface monitor we get the collected item modeled like this:
class cfs_ser_item_mon extends uvm_sequence_item;
//time when data was sampled
time sample_time;

//sampled value of the serial data line
bit data;
...
endclass

From the parallel interface monitor we get the collected item modeled like this:
class cfs_des_item_mon extends uvm_sequence_item;
//parallel data
byte data;
...
endclass

A typical scoreboard implementation might look like this:
class cfs_scoreboard extends uvm_component;
...
//buffer getting bits of information from serial interface
bit serial_bits[$];

//model of the internal fifo
byte fifo[$];
...
//analysis port implementation for getting data from the serial monitor
virtual function void write_ser(cfs_ser_item_mon serial_info);
serial_bits.push_back(serial_info.data);
if(serial_bits.size() == 8) begin
fifo.push_back(byte'({<<{serial_bits}})); serial_bits = {}; end endfunction //analysis port implementation for getting data from the parallel monitor virtual function void write_des(cfs_des_item_mon parallel_info); byte expected_data = fifo.pop_front(); if(expected_data != parallel_info.data) begin `uvm_fatal("DUT_ERROR", $sformatf("Data mismatch detected - expected: %0x, received: %0x", expected_data, parallel_info.data)) end endfunction endclass

For the waveforms above we detect that the byte from time 495ns is wrong and we get the following error message:
Data mismatch detected - expected: 54, received: 7c
Now we must check the waveforms to see if we really sent on the serial interface byte 0x54 - good luck finding that one on the waveforms ๐Ÿ™‚

The Good Way

The big problem with the first method is that we stripped down a lot of useful information when we pushed the byte value in our FIFO model. A better alternative is to model our FIFO in such a way that we remember the cause of the expected output byte:
class cfs_fifo_item extends uvm_object;
//original information received from the serial monitor
cfs_ser_item_mon serial_infos[$];

virtual function byte get_data();
bit serial_bits[$];

for(int i = 0; i < 8; i++) begin serial_bits.push_back(serial_infos[i].data); end return byte'({<<{serial_bits}}); endfunction endclass

Our scoreboard can now look like this:
class cfs_scoreboard extends uvm_component;
...
//buffer getting information from serial interface
cfs_ser_item_mon serial_infos[$];

//model of the internal fifo
cfs_fifo_item fifo[$];
...
//analysis port implementation for getting data from the serial monitor virtual
function void write_ser(cfs_ser_item_mon serial_info);
serial_infos.push_back(serial_info);
if(serial_infos.size() == 8) begin
cfs_fifo_item fifo_item = cfs_fifo_item::type_id::create("fifo_item");
fifo_item.serial_infos = serial_infos;
fifo.push_back(fifo_item);
serial_infos = {};
end
endfunction

//analysis port implementation for getting data from the parallel monitor
virtual function void write_des(cfs_des_item_mon parallel_info);
cfs_fifo_item fifo_item = fifo.pop_front();

byte expected_data = fifo_item.get_data();

if(expected_data != parallel_info.data) begin
//build the mismatch information on bit level
string mismatch_info = "";
for(int i = 0; i < 8; i++) begin if(expected_data[i] != parallel_info.data[i]) begin mismatch_info = $sformatf("%s\t\tbit %0d with value %0b sampled at %0dns\n", mismatch_info, i, expected_data[i], fifo_item[7 - i].sample_time); end end `uvm_fatal("DUT_ERROR", $sformatf("Data mismatch detected - expected: %0x, received: %0x\n\t- byte present on serial interface in interval [%0dns..%0dns]\n\t- bit level information: %s", expected_data, parallel_info.data, fifo_item[0].sample_time, fifo_item[7].sample_time, mismatch_info)) end endfunction endclass

For the same waveforms we now get the following DUT error message:
Data mismatch detected - expected: 54, received: 7c
- byte present on serial interface in interval [325ns..465ns] - bit level information:
bit 3 with value 0 sampled at 405ns
bit 5 with value 0 sampled at 365ns

With an error message like this one checking the correct behavior of the environment is much easier and the debugging time decreases quite a lot.

DUT error details
DUT error details
On the other side, this approach does imply using more memory during simulations and one must argue that this will make the simulation slower. However I'm willing to bet that the extra time for simulating is much smaller than the time gain in debugging ๐Ÿ™‚




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