Debugging Tip: Always Remember The Cause

Debugging Tip: Always Remember The Cause

Posted by

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:

1
2
3
4
5
6
7
8
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:

1
2
3
4
5
class cfs_des_item_mon extends uvm_sequence_item;
   //parallel data
   byte data;
   ...
endclass

A typical scoreboard implementation might look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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:

1
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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:

1
2
3
4
5
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 🙂



Cristian Slav

Leave a Reply

Your email address will not be published.