SystemVerilog TestBench Example - ADDER


-: Internal Links :-       
 Transactor                 
 Generator                  
 Driver                         
 Environment         
 Test                            
 Interface                    
 TestBench Top          
 EDAPlayground Link  

 'ADDER' TestBench Without Monitor, Agent and Scoreboard 


TestBench Architecture:

 Transaction Class:                                                                                                                             
  • Fields required to generate the stimulus are declared in the transaction class.
  • Transaction class can also be used as placeholder for the activity monitored by monitor on DUT signals.
  • So, first step is to declare the 'Fields' in the transaction class.
  • Below are the steps to write transaction class.
1. Declaring the fields.

class transaction;

  //declaring the transaction items
  bit [3:0] a;
  bit [3:0] b;
  bit [6:0] c;
  
endclass


2. To generate the random stimulus, declare the fields as 'rand'.

class transaction;

  //declaring the transaction items
  rand bit [3:0] a;
  rand bit [3:0] b;
       bit [7:0] c;
  
endclass


3. Adding display() method to display Transaction properties.

class transaction;
  
  //declaring the transaction items
  rand bit [3:0] a;
  rand bit [3:0] b;
       bit [6:0] c;
  function void display(string name);
    $display("-------------------------");
    $display("- %s ",name);
    $display("-------------------------");
    $display("- a = %0d, b = %0d",a,b);
    $display("- c = %0d",c);
    $display("-------------------------");
  endfunction
endclass

 Generator Class:                                                                                                                              

Generator class is responsible for,
  • Generating the stimulus by randomizing the transaction class
  • Sending the randomized class to driver

class generator;

  ------  
  
endclass


1. Declare the transaction class handle,
class generator;
  
  //declaring transaction class 
  rand transaction trans;
  
endclass


2. 'Randomize' the transaction class,
class generator;
  
  //declaring transaction class 
  rand transaction trans;
  
  //main task, generates(create and randomizes) the packets and puts into mailbox
  task main();
      trans = new();
      if( !trans.randomize() ) $fatal("Gen:: trans randomization failed"); 
      gen2driv.put(trans);
  endtask
  
endclass


3. Adding Mailbox and event,
    Mailbox is used to send the randomized transaction to Driver.
    Event to indicate end of packet generation.

This involves,
  • Declaring the Mailbox and Event
  • Getting the Mailbox handle from env class ( because, same mailbox will be shared across generator and driver).
class generator;
  
  //declaring transaction class 
  rand transaction trans;
  
  //declaring mailbox
  mailbox gen2driv;  

  //event, to indicate the end of transaction generation
  event ended;

  //constructor
  function new(mailbox gen2driv);
    //getting the mailbox handle from env
    this.gen2driv = gen2driv;
  endfunction
  
  //main task, generates(create and randomizes) the packets and puts into mailbox
  task main();
      trans = new();
      if( !trans.randomize() ) $fatal("Gen:: trans randomization failed"); 
      gen2driv.put(trans);
      -> ended; //triggering indicatesthe end of generation
  endtask
  
endclass


4. Adding variable to control the number of packets to be created,
class generator;
  
  //declaring transaction class 
  rand transaction trans;
  
  //declaring mailbox
  mailbox gen2driv;

  //event, to indicate the end of transaction generation
  event ended;

  //repeat count, to specify number of items to generate
  int  repeat_count;  

  //constructor
  function new(mailbox gen2driv);
    //getting the mailbox handle from env
    this.gen2driv = gen2driv;
  endfunction
  
  //main task, generates(create and randomizes) the repeat_count number of transaction packets and puts into mailbox
  task main();
    repeat(repeat_count) begin
      trans = new();
      if( !trans.randomize() ) $fatal("Gen:: trans randomization failed"); 
      gen2driv.put(trans);
    end
    -> ended; //triggering indicatesthe end of generation
  endtask
  
endclass


5. Adding event to indicate the completion of generation process, event will be triggered on the completion of Generation process.

class generator;
  
  //declaring transaction class 
  rand transaction trans;
  
  //declaring mailbox
  mailbox gen2driv;
  
  //repeat count, to specify number of items to generate
  int  repeat_count;  

  //event, to indicate the end of transaction generation
  event ended;

  //constructor
  function new(mailbox gen2driv);
    //getting the mailbox handle from env
    this.gen2driv = gen2driv;
  endfunction
  
  //main task, generates(create and randomizes) the repeat_count number of transaction packets and puts into mailbox
  task main();

    repeat(repeat_count) begin
      trans = new();
      if( !trans.randomize() ) $fatal("Gen:: trans randomization failed");    
      gen2driv.put(trans);
    end
    -> ended; //triggering indicatesthe end of generation
  endtask  
endclass

 Interface:                                                                                                                                        
Interface will group the signals.
This is a simple interface without modport and clocking block.

interface intf(input logic clk,reset);
  ----
endinterface

1. Complete Interface code,
interface intf(input logic clk,reset);
  
  //declaring the signals
  logic       valid;
  logic [3:0] a;
  logic [3:0] b;
  logic [6:0] c;
  
endinterface


 Driver Class:                                                                                                                                        
Driver class is responsible for,
  • receive the stimulus generated from generator and drive to DUT by assigning transaction class values to interface signals.
class driver;
  ----
endclass

1. Declare interface and mailbox, Get the interface and mailbox handle through constructor.
  //creating virtual interface handle
  virtual intf vif;

  //creating mailbox handle
  mailbox gen2driv;

  //constructor
  function new(virtual intf vif,mailbox gen2driv);
    //getting the interface
    this.vif = vif;
    //getting the mailbox handle from  environment 
    this.gen2driv = gen2driv;
  endfunction

2. Adding reset task, which initializes the Interface signals to default values.

  //Reset task, Reset the Interface signals to default/initial values
  task reset;
    wait(vif.reset);
    $display("[ DRIVER ] ----- Reset Started -----");
    vif.a <= 0;
    vif.b <= 0;
    vif.valid <= 0;
    wait(!vif.reset);
    $display("[ DRIVER ] ----- Reset Ended   -----");
  endtask

3. Adding drive task to drive the transaction packet to interface signal.
  //drive the transaction items to interface signals
  task drive;
      transaction trans;
      gen2driv.get(trans);
      @(posedge vif.clk);
      vif.valid <= 1;
      vif.a     <= trans.a;
      vif.b     <= trans.b;
      @(posedge vif.clk);
      vif.valid <= 0;
      trans.c   <= vif.c;
      @(posedge vif.clk);
      trans.display("[ Driver ]");
      no_transactions++;
    end
  endtask

4. Adding local variable to track the number of packets driven, and increment the variable in drive task.
(This will be useful to end the test-case/Simulation. i.e compare the generated pkt's and driven pkt's, if both are same then end the simulation)

  //used to count the number of transactions
  int no_transactions;

  //drive the transaction items to interface signals
  task drive;
    ------
    ------
    no_transactions++;
  endtask

5. Complete driver code.
class driver;
 
  //used to count the number of transactions
  int no_transactions;
  
  //creating virtual interface handle
  virtual intf vif;
  
  //creating mailbox handle
  mailbox gen2driv;
  
  //constructor
  function new(virtual intf vif,mailbox gen2driv);
    //getting the interface
    this.vif = vif;
    //getting the mailbox handles from  environment 
    this.gen2driv = gen2driv;
  endfunction
  
  //Reset task, Reset the Interface signals to default/initial values
  task reset;
    wait(vif.reset);
    $display("[ DRIVER ] ----- Reset Started -----");
    vif.a <= 0;
    vif.b <= 0;
    vif.valid <= 0;
    wait(!vif.reset);
    $display("[ DRIVER ] ----- Reset Ended   -----");
  endtask
  
  //drivers the transaction items to interface signals
  task main;
    forever begin
      transaction trans;
      gen2driv.get(trans);
      @(posedge vif.clk);
      vif.valid <= 1;
      vif.a     <= trans.a;
      vif.b     <= trans.b;
      @(posedge vif.clk);
      vif.valid <= 0;
      trans.c   <= vif.c;
      @(posedge vif.clk);
      trans.display("[ Driver ]");
      no_transactions++;
    end
  endtask
          
endclass


 Environment:                                                                                                                                    
Environment is container class contains MailboxGenerator and Driver.

Creates the mailbox, generator and driver, shares the mailbox handle across the Generator and Driver.

class environment;
  ---
endclass


1. Declare the handles,
  //generator and driver instance
  generator gen;
  driver    driv;
  
  //mailbox handle's
  mailbox gen2driv;
   
  //virtual interface
  virtual intf vif;


2. In Construct Method, Create
  • Mailbox
  • Generator
  • Driver 
and pass the interface handle through new() method.

  //constructor
  function new(virtual intf vif);
    //get the interface from test
    this.vif = vif;
    
    //creating the mailbox (Same handle will be shared across generator and driver)
    gen2driv = new();
    
    //creating generator and driver
    gen  = new(gen2driv);
    driv = new(vif,gen2driv);
  endfunction


3. For better accessibility.
Generator and Driver activity can be divided and controlled in three methods.

  • pre_test()   - Method to call Initialization. i.e, reset method.
  • test()         - Method to call Stimulus Generation and Stimulus Driving. 
  • post_test() - Method to wait the completion of generation and driving.

  task pre_test();
    driv.reset();
  endtask
  
  task test();
    fork 
      gen.main();
      driv.main();
    join_any
  endtask
  
  task post_test();
    wait(gen.ended.triggered);
    wait(gen.repeat_count == driv.no_transactions);
  endtask 


4. Add run task to call the above methods,
call $finish after post_test() to end the simulation.
  task run;
    pre_test();
    test();
    post_test();
    $finish;
  endtask


5. Complete environment class code.
`include "transaction.sv"
`include "generator.sv"
`include "driver.sv"
class environment;
  
  //generator and driver instance
  generator gen;
  driver    driv;
  
  //mailbox handle's
  mailbox gen2driv;
  
  //virtual interface
  virtual intf vif;
  
  //constructor
  function new(virtual intf vif);
    //get the interface from test
    this.vif = vif;
    
    //creating the mailbox (Same handle will be shared across generator and driver)
    gen2driv = new();
    
    //creating generator and driver
    gen  = new(gen2driv);
    driv = new(vif,gen2driv);
  endfunction
  
  //
  task pre_test();
    driv.reset();
  endtask
  
  task test();
    fork 
    gen.main();
    driv.main();
    join_any
  endtask
  
  task post_test();
    wait(gen.ended.triggered);
    wait(gen.repeat_count == driv.no_transactions);
  endtask  
  
  //run task
  task run;
    pre_test();
    test();
    post_test();
    $finish;
  endtask
  
endclass

 Test:                                                                                                                                              
Test code is written with the program block.

Test is responsible for,

  • Creating the environment.
  • Configuring the testbench i.e, setting the type and number of transactions to be generated. 
  • Initiating the stimulus driving.

program test;
  ----
endprogram


1. Declare and Create environment,
  //declaring environment instance
  environment env;
  
  initial begin
    //creating environment
    env = new(intf);
  end


2. Configure the number of transactions to be generated,
    //setting the repeat count of generator as 10, means to generate 10 packets
    env.gen.repeat_count = 10;


3. Initiating the stimulus driving,
    //calling run of env, it interns calls generator and driver main tasks.
    env.run();


4. Complete Test Code,
`include "environment.sv"
program test(intf intf);
  
  //declaring environment instance
  environment env;
  
  initial begin
    //creating environment
    env = new(intf);
    
    //setting the repeat count of generator as 10, means to generate 10 packets
    env.gen.repeat_count = 10;
    
    //calling run of env, it interns calls generator and driver main tasks.
    env.run();
  end
endprogram

  TestBench Top:                                                                                                                                     

  • This is the top most file, which connects the DUT and TestBench. 
  • TestBench top consists of DUT, Test and Interface instances.
  • Interface connects the DUT and TestBench.

module tbench_top;
  ---
endmodule


1.Declare and Generate the clock and reset,
  //clock and reset signal declaration
  bit clk;
  bit reset;
  
  //clock generation
  always #5 clk = ~clk;
  
  //reset Generation
  initial begin
    reset = 1;
    #5 reset =0;
  end


2. Create Interface instance,
  //creatinng instance of interface, in-order to connect DUT and testcase
  intf intf(clk,reset);


3. Create Design Instance and Connect Interface signals,
  //DUT instance, interface signals are connected to the DUT ports
  adder DUT (
    .clk(i_intf.clk),
    .reset(i_intf.reset),
    .a(i_intf.a),
    .b(i_intf.b),
    .valid(i_intf.valid),
    .c(i_intf.c)
   );


4. Create test instance and Pass the interface handle,
  //Testcase instance, interface handle is passed to test as an argument
  test t1(intf);


5. Add logic to generate the dump,
  initial begin 
    $dumpfile("dump.vcd"); $dumpvars;
  end


6. Complete testbench top code,
`include "interface.sv"
`include "random_test.sv"

module tbench_top;
  
  //clock and reset signal declaration
  bit clk;
  bit reset;
  
  //clock generation
  always #5 clk = ~clk;
  
  //reset Generation
  initial begin
    reset = 1;
    #5 reset =0;
  end
  
  
  //creatinng instance of interface, inorder to connect DUT and testcase
  intf i_intf(clk,reset);
  
  //Testcase instance, interface handle is passed to test as an argument
  test t1(i_intf);
  
  //DUT instance, interface signals are connected to the DUT ports
  adder DUT (
    .clk(i_intf.clk),
    .reset(i_intf.reset),
    .a(i_intf.a),
    .b(i_intf.b),
    .valid(i_intf.valid),
    .c(i_intf.c)
   );
  
  //enabling the wave dump
  initial begin 
    $dumpfile("dump.vcd"); $dumpvars;
  end
endmodule


Edit and Execute Simple Adder TestBench code in EDA Playground.


Execute the above code on https://www.edaplayground.com/x/2pYJ

Click on EDA playground image.

Leave your Comments here,

"Your valuable inputs are required to improve the quality"