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.

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.

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โ






Add comment