We need to have an environment known as a testbench to run any kind of simulation on the design. The example shown in Preface is not modular, scalable, flexible or even re-usable because of the way DUT is connected, and how signals are driven. Let's take a look at a simple testbench and try to understand about the various components that facilitate data transfer from/to the DUT.


Is DUT the design ?

Yes. DUT (Design under Test) is the design itself, written in verilog/vhdl, and will be instantiated in the top testbench module. Although we call it DUT, its actually DUV (Design Under Verification) and DUT is a term used for performing ATPG patterns on a tester after the silicon is back from foundry. For our ease of use, we'll call it a DUT. In verilog, we used to connect the DUT ports with wires.

module tb_top;
  reg clk;
  wire en;
  wire wr;
  wire data;
  design myDsn ( .clk (clk),
                 .en  (en),
                 .wr  (wr),
                 . ...
  // Rest of the testbench

What is an interface ?

If our design contained hundreds of port signals, it would be cumbersome to connect, maintain and re-use the signals. Instead, we can put all DUT signals in a block, and call it an Interface to the DUT. So, now all interactions with the DUT will be made via this interface.

Driver ?

The Driver is the component that does the pin-wiggling of the DUT, through a task defined in the interface. So the driver does not necessarily know or deal with the signals in the DUT because now we are at a higher level of abstraction provided by the interface.

But how does the driver know what to drive ?

That's why the Generator is plugged in. The main job of the generator is to create valid data transactions that can be sent to the driver. For all this to happen, we need to create an abstraction of the data itself. This is done by implementing Object-Oriented-Programming concepts. We can consider the data that we want to send as a single entity/object. This is shown by the blue squares in the image. It is the job of the driver to get the data object and translate it into something the DUT can understand.

Why do I need a Monitor ?

We have gotten to the point where we can send data to the DUT. But that's only half way through, because our primary aim is to verify the design. The DUT reacts to the input data by sending the processed data to the output pins. The Monitor picks up the processed data, converts it into a data object and sends it to the scoreboard.

What does the scoreboard do ?

The Scoreboard will have tasks and functions which can process the data (behave the same way as DUT) it received from the driver and will compare the predicted results with the actual data obtained from the Monitor.

Why are all these components put in Env ?

So, we have created an environment block that can house all the verification components. Moreover, it becomes easier to re-use the same environment when we need it, because design related signals are not present in the environment. It is also flexible and scalable now because more components can be plugged into the same environment.

And the test uses the environment object ?

Exactly. The test will instantiate an object of the environment and configure it the way the test wants to. Remember that we will most probably have thousands of tests and it is not feasbile to make direct changes to the environment for each test. Instead we want certain knobs/parameters in the environment that can be tweaked for each test. That way, the test will have a higher control over stimulus generation and will be more effective.

So, is that it ?

Here, we have talked about how a simple testbench looks like. In real projects, there'll be many such components plugged in to do various tasks at higher levels of abstraction. If we had to verify a simple digital counter with maximum 50 lines of RTL code, yea, this would suffice. But, when complexity increases, there will be a need to deal with more abstraction.

What do you mean by higher levels of abstraction ?

In the Preface, you saw that we toggled the design using individual signals.

#5  resetn <= 0;
#20 resetn <= 1;
Instead, if you put these two signals in a task and call it "apply_reset" task, you have just created a component that can be re-used and hides the details of what signals and what time intervals it is being asserted. This is a feature we would like to have when developing the testbench - to hide away details - so that the test writer need not bother about the how and instead focus on when and why these tasks should be put to use. A test writer finally uses tasks, configures environment and writes code to test the design.
module tb_top;
  bit resetn;
  task apply_reset ();
    #5  resetn <= 0;
    #20 resetn <= 1;
  initial begin

Was this article helpful ?