SystemVerilog TestBench Example – ADDER

‘ADDER’ TestBench Without Monitor, Agent and Scoreboard

TestBench Architecture

SystemVerilog simple TestBench block diagram
SystemVerilog simple TestBench block diagram

Transaction Class

  • Fields required to generate the stimulus are declared in the transaction class.
  • Transaction class can also be used as a placeholder for the activity monitored by the monitor on DUT signals.
  • So, the first step is to declare the ‘Fields‘ in the transaction class.
  • Below are the steps to write the 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 the end of packet generation.

This involves,

  • Declaring the Mailbox and Event
  • Getting the Mailbox handle from the env class ( because the 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 a 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 an event to indicate the completion of the generation process, the event will be triggered on the completion of the 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 the 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 a 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 a 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 a drive task to drive the transaction packet to the 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 a local variable to track the number of packets driven, and increment the variable in the 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 Mailbox, Generator 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 the 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 a 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.

The 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 an 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 topmost file, which connects the DUT and TestBench.
  • TestBench top consists of DUT, Test and Interface instances.
  • The 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 a 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

Click to execute on   

❮ Previous