SourceSV Verification Directory

A layered testbench is a modular verification environment where the components are divided into specific layers, with each layer responsible for a distinct task. This structured approach ensures better organization, reusability, and scalability, making it easier to manage complex verification environments. The typical components of a SystemVerilog testbench are illustrated in Figure 1.

Figure 1: Testbench Environment

Figure 1: Testbench Environment

Let’s explore the SystemVerilog verification components using a Half Adder as an example.

Top

The top module acts as a wrapper that connects the Design Under Test (DUT) to the testbench environment, ensuring seamless communication between the two.

  • Example

    module tb_half_adder;
    
      /*AUTOWIRE*/
      // Beginning of automatic wires (for undeclared instantiated-module outputs)
      wire			c;			// From DUT of half_adder.v
      wire			s;			// From DUT of half_adder.v
      // End of automatics
    
      intf i_intf();	//interface handle
    
      test t1(i_intf);	//connect test class handle to interface
    
      half_adder DUT (/*AUTOINST*/
                      // Outputs
                      .s			(i_intf.s),
                      .c			(i_intf.c),
                      // Inputs
                      .a			(i_intf.a),
                      .b			(i_intf.b));
    
      initial begin
        $dumpfile("dump.vcd");
        $dumpvars;
      end
    
    endmodule
    

Design Under Test (DUT)

The Design Under Test (DUT) refers to the actual hardware design or module that is being verified within a simulation environment. The DUT is the focus of the verification process, and it can be any digital design component, such as a microprocessor, a memory block, or a custom logic circuit.

  • Example

    module half_adder(a,b,s,c);
      input  a,b;
      output s,c;
    
      xor X1 (s,a,b);
      and A1 (c,a,b);
    
    endmodule
    

Test

The test component creates an instance of the environment and configures it for the simulation. Multiple test components can be defined to handle different test cases, ensuring comprehensive verification of the DUT under various scenarios.

  • Example

    `include "environment.sv"
    
    program test(intf i_intf);
    
      environment env;	   //class handle
    
      initial begin
        env = new(i_intf); //class object
        env.run();	       //class task
      end
    
    endprogram
    

Interface

All the signals are grouped under a single component, allowing them to be shared across different testbench components using a single reference. This approach simplifies the testbench by eliminating the need to reference each signal individually, promoting cleaner and more maintainable code.

  • Example

    interface intf();
    
      logic a;
      logic b;
      logic s;
      logic c;
    
    endinterface
    

Clocking Block

A clocking block in a testbench is used to manage the timing relationships and synchronization of a group of signals. It provides a clear and organized way to define clocking events, control the sampling and driving of signals, and ensure synchronized operations within the testbench.

Environment

The environment component acts as the top-module for all the key components of a verification environment. It provides a modular structure by instantiating and connecting drivers, monitors, generators, scoreboards, and interfaces.

  • Example

    `include "transaction.sv"
    `include "generator.sv"
    `include "driver.sv"
    `include "monitor.sv"
    `include "scoreboard.sv"
    
    class environment;
    
      generator		gen;	//create the handle
      driver		drv;
      monitor		mon;
      scoreboard	scb;
    
      mailbox m1;			//mailbox : Generator to Driver
      mailbox m2;			//mailbox : Monitor to Scoreboard
    
      event ev;
    
      virtual intf vif;
    
      function new (virtual intf vif);
        this.vif = vif;
    
        m1 = new();		//create the mailbox for connection
        m2 = new();
    
        gen = new(m1);
        drv = new(vif,m1);
        mon = new(vif,m2);
        scb = new(m2);
    
        gen.ev = ev;    //connect event from generator class
        scb.ev = ev;    //to scoreboard class
      endfunction
    
    
      task test();		//performs the task in the classes
        fork
          gen.main();
          drv.main();
          mon.main();
          scb.main();
        join_any
      endtask
    
      task run();		//main which calls test() task
        test();
        $finish;
      endtask
    
    endclass
    

Generator

The generator is key component responsible for creating stimulus or transactions that drive the DUT. It can generate both randomized and directed inputs. The generator is typically connected to the driver, which sends the generated stimulus to the DUT.

  • Example

    class generator;
    
      event ev;              // Create event to wait until the scr is verified the transaction
    
      mailbox gen2drv;       //Create a mailbox: sending info from Generator to Driver
    
      transaction trans;     //Create an handle of the transaction class
    
    //Create a constructor of generator class
    //"new" will create a memory for  variable for gen2drv and initialize
    //them with their default values and return the address to generator handle
      function new(mailbox gen2drv);
        trans = new(); 					//object for transaction class
        this.gen2drv = gen2drv; 		//info from the outside class is passed to the
      endfunction						//gen2drv present inside this class
    
      task main();
        repeat(10)
          begin
            assert(trans.randomize)
              else
            $error("GEN: Randomization Failed");  //randomize the transaction and verify if is randomized
            //trans.randomize();			      //randomize the transaction
            trans.DISP("GEN");	                  //Display the values
            gen2drv.put(trans);			          //put them in the mailbox
    
            @(ev);                                // wait for the event to be triggered in scoreboard class
          end
      endtask
    
    endclass
    

Driver

The driver component is responsible for translating the stimulus generated by the generator into actual signal activity on the DUT. It interacts with the DUT’s via interface (virutal interface) and ensures that the signals are driven correctly according to the specified protocols.

  • Example

    class driver;
    
      virtual intf vif;			//vif is the handle of virtual interface
    
      mailbox gen2drv;			//handle of the mailbox
    
      transaction trans;	    //handle of transaction class, to get the data from mailbox
    
      function new (virtual intf vif, mailbox gen2drv);
        this.vif = vif;
        this.gen2drv = gen2drv;
      endfunction
    
      task main;
        forever
          begin
            gen2drv.get(trans); //receive the info from generator
    
            vif.a <= trans.a;	//sample the input for the virtual interface
            vif.b <= trans.b;
    
            trans.s = vif.s;	//sample the output from the virtual interface
            trans.c = vif.c;	//sampling the output may not be required here
    
            trans.DISP("DRV");
          end
      endtask
    
    endclass
    

Monitor

The monitor component is a vital part of the verification environment responsible for observing and collecting data from the DUT. The monitor simply observes and records the signals for later verification.

  • Example

    class monitor;
    
      virtual intf vif;		//declare virtual interface
    
      mailbox mon2scb;		//declare mailbox to transfer info from monitor to scoreboard
    
      transaction trans;	//handle for transaction class
    
      function new (virtual intf vif, mailbox mon2scb);
        this.vif = vif;
        trans = new();
        this.mon2scb = mon2scb;     //constructor or create an object for transaction class
    
      endfunction
    
      task main;
        forever
            begin
              #3;				    //sample after 3 ns
    
              trans.a = vif.a;		//send the data from virtual interface to monitor
              trans.b = vif.b;
    
              trans.s = vif.s;
              trans.c = vif.c;
    
              mon2scb.put(trans);	//put the info to send to scoreboard
    
              trans.DISP("MON");
        end
      endtask
    
    endclass
    

Scoreboard

The scoreboard component in the verification environment validates the outputs of the DUT against the expected results. It acts as a comparison mechanism, collecting data from the DUT and comparing it to the expected outcomes to determine if the DUT is functioning correctly.

  • Example

    class scoreboard;
    
      event ev;            //Create event to trigger the next stimulus in generator class
    
      mailbox mon2scb;     //Create a mailbox: sending info from Monitor to Scroeboard
    
      transaction trans;   //Create an handle of the transaction class
    
      function new (mailbox mon2scb);
        this.mon2scb = mon2scb;
      endfunction
    
      task main;
        forever
          begin
            mon2scb.get(trans);
    
            trans.DISP("SCR");
    
            if( ((trans.a ^ trans.b) == trans.s) && ((trans.a & trans.b) == trans.c) )
              $display("Verification Passed! :)");
            else
              $error("Verification Failed! :(");
    
            $display("-----------------------");
            ->ev;         //trigger an event to proceed to the next stimulus
          end
      endtask
    
    endclass
    

Execute the code in EDA Playground