SystemVerilog TestBench Example 01



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

 Memory Model 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 [1:0] addr;
  bit       wr_en;
  bit       rd_en;
  bit [7:0] wdata;
  bit [7:0] rdata;
  bit [1:0] cnt;
  
endclass


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

class transaction;

  //declaring the transaction items
  rand bit [1:0] addr;
  rand bit       wr_en;
  rand bit       rd_en;
  rand bit [7:0] wdata;
       bit [7:0] rdata;
       bit [1:0] cnt;
  
endclass


3. Either write or read operation will be performed at once, so wr_en or rd_en is generated by 'adding constraint'.

class transaction;

  //declaring the transaction items
  rand bit [1:0] addr;
  rand bit       wr_en;
  rand bit       rd_en;
  rand bit [7:0] wdata;
       bit [7:0] rdata;
       bit [1:0] cnt;
  
  //constaint, to generate any one among write and read
  constraint wr_rd_c { wr_en != rd_en; }; 
  
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. Mailbox is used to send the randomized transaction to Driver,

This involves,
  • Declaring the Mailbox
  • 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;  

  //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);
  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;
  
  //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
  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
  event ended;

  //constructor
  function new(mailbox gen2driv,event ended);
    //getting the mailbox handle from env
    this.gen2driv = gen2driv;
    this.ended    = ended;
  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; 
  endtask  
endclass

 Interface:                                                                                                                                        
Interface will group the signals, specifies the direction (Modport) and Synchronize the signals(Clocking Block).

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

1. Driver Clocking Block,
  //driver clocking block
  clocking driver_cb @(posedge clk);
    default input #1 output #1;
    output addr;
    output wr_en;
    output rd_en;
    output wdata;
    input  rdata;  
  endclocking


2. Monitor Clocking Block,
  //monitor clocking block
  clocking monitor_cb @(posedge clk);
    default input #1 output #1;
    input addr;
    input wr_en;
    input rd_en;
    input wdata;
    input rdata;  
  endclocking


3. Driver and Monitor modport,
  //driver modport
  modport DRIVER  (clocking driver_cb,input clk,reset);
  
  //monitor modport  
  modport MONITOR (clocking monitor_cb,input clk,reset);


4. Complete Interface code,
interface mem_intf(input logic clk,reset);
  
  //declaring the signals
  logic [1:0] addr;
  logic wr_en;
  logic rd_en;
  logic [7:0] wdata;
  logic [7:0] rdata;
  
  //driver clocking block
  clocking driver_cb @(posedge clk);
    default input #1 output #1;
    output addr;
    output wr_en;
    output rd_en;
    output wdata;
    input  rdata;  
  endclocking
  
  //monitor clocking block
  clocking monitor_cb @(posedge clk);
    default input #1 output #1;
    input addr;
    input wr_en;
    input rd_en;
    input wdata;
    input rdata;  
  endclocking
  
  //driver modport
  modport DRIVER  (clocking driver_cb,input clk,reset);
  
  //monitor modport  
  modport MONITOR (clocking monitor_cb,input clk,reset);
  
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 mem_intf mem_vif;

  //creating mailbox handle
  mailbox gen2driv;

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

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

For simplicity, define is used to access interface signals.

    `define DRIV_IF mem_vif.DRIVER.driver_cb 

`DRIV_IF will point to mem_vif.DRIVER.driver_cb 

  //Reset task, Reset the Interface signals to default/initial values
  task reset;
    wait(mem_vif.reset);
    $display("--------- [DRIVER] Reset Started ---------");
    `DRIV_IF.wr_en <= 0;
    `DRIV_IF.rd_en <= 0;
    `DRIV_IF.addr  <= 0;
    `DRIV_IF.wdata <= 0;        
    wait(!mem_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;
    forever begin
      transaction trans;
      `DRIV_IF.wr_en <= 0;
      `DRIV_IF.rd_en <= 0;
      gen2driv.get(trans);
      $display("--------- [DRIVER-TRANSFER: %0d] ---------",no_transactions);
      @(posedge mem_vif.DRIVER.clk);
        `DRIV_IF.addr <= trans.addr;
      if(trans.wr_en) begin
        `DRIV_IF.wr_en <= trans.wr_en;
        `DRIV_IF.wdata <= trans.wdata;
        $display("\tADDR = %0h \tWDATA = %0h",trans.addr,trans.wdata);
        @(posedge mem_vif.DRIVER.clk);
      end
      if(trans.rd_en) begin
        `DRIV_IF.rd_en <= trans.rd_en;
        @(posedge mem_vif.DRIVER.clk);
        `DRIV_IF.rd_en <= 0;
        @(posedge mem_vif.DRIVER.clk);
        trans.rdata = `DRIV_IF.rdata;
        $display("\tADDR = %0h \tRDATA = %0h",trans.addr,`DRIV_IF.rdata);
      end
      $display("-----------------------------------------");
      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 mem_intf mem_vif;
  
  //creating mailbox handle
  mailbox gen2driv;
  
  //constructor
  function new(virtual mem_intf mem_vif,mailbox gen2driv);
    //getting the interface
    this.mem_vif = mem_vif;
    //getting the mailbox handle from  environment 
    this.gen2driv = gen2driv;
  endfunction
  
  //Reset task, Reset the Interface signals to default/initial values
  task reset;
    wait(mem_vif.reset);
    $display("--------- [DRIVER] Reset Started ---------");
    `DRIV_IF.wr_en <= 0;
    `DRIV_IF.rd_en <= 0;
    `DRIV_IF.addr  <= 0;
    `DRIV_IF.wdata <= 0;        
    wait(!mem_vif.reset);
    $display("--------- [DRIVER] Reset Ended---------");
  endtask
  
  //drive the transaction items to interface signals
  task drive;
    forever begin
      transaction trans;
      `DRIV_IF.wr_en <= 0;
      `DRIV_IF.rd_en <= 0;
      gen2driv.get(trans);
      $display("--------- [DRIVER-TRANSFER: %0d] ---------",no_transactions);
      @(posedge mem_vif.DRIVER.clk);
        `DRIV_IF.addr <= trans.addr;
      if(trans.wr_en) begin
        `DRIV_IF.wr_en <= trans.wr_en;
        `DRIV_IF.wdata <= trans.wdata;
        $display("\tADDR = %0h \tWDATA = %0h",trans.addr,trans.wdata);
        @(posedge mem_vif.DRIVER.clk);
      end
      if(trans.rd_en) begin
        `DRIV_IF.rd_en <= trans.rd_en;
        @(posedge mem_vif.DRIVER.clk);
        `DRIV_IF.rd_en <= 0;
        @(posedge mem_vif.DRIVER.clk);
        trans.rdata = `DRIV_IF.rdata;
        $display("\tADDR = %0h \tRDATA = %0h",trans.addr,`DRIV_IF.rdata);
      end
      $display("-----------------------------------------");
      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;
  
  //event for synchronization between generator and test
  event gen_ended;
  
  //virtual interface
  virtual mem_intf mem_vif;


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

  //constructor
  function new(virtual mem_intf mem_vif);
    //get the interface from test
    this.mem_vif = mem_vif;
    
    //creating the mailbox (Same handle will be shared across generator and driver)
    gen2driv = new();
    
    //creating generator and driver
    gen = new(gen2driv,gen_ended);
    driv = new(mem_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;
  
  //event for synchronization between generator and test
  event gen_ended;
  
  //virtual interface
  virtual mem_intf mem_vif;
  
  //constructor
  function new(virtual mem_intf mem_vif);
    //get the interface from test
    this.mem_vif = mem_vif;
    
    //creating the mailbox (Same handle will be shared across generator and driver)
    gen2driv = new();
    
    //creating generator and driver
    gen = new(gen2driv,gen_ended);
    driv = new(mem_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(mem_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, inorder to connect DUT and testcase
  mem_intf intf(clk,reset);


3. Create Design Instance and Connect Interface signals,
  //DUT instance, interface signals are connected to the DUT ports
  memory DUT (
    .clk(intf.clk),
    .reset(intf.reset),
    .addr(intf.addr),
    .wr_en(intf.wr_en),
    .rd_en(intf.rd_en),
    .wdata(intf.wdata),
    .rdata(intf.rdata)
   );


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
  mem_intf intf(clk,reset);
  
  //Testcase instance, interface handle is passed to test as an argument
  test t1(intf);
  
  //DUT instance, interface signals are connected to the DUT ports
  memory DUT (
    .clk(intf.clk),
    .reset(intf.reset),
    .addr(intf.addr),
    .wr_en(intf.wr_en),
    .rd_en(intf.rd_en),
    .wdata(intf.wdata),
    .rdata(intf.rdata)
   );
  
  //enabling the wave dump
  initial begin 
    $dumpfile("dump.vcd"); $dumpvars;
  end
endmodule


Edit and Execute the Memory Model TestBench code in EDA Playground.


Execute the above code on 

Click on EDA playground image.

Leave your Comments here,

"Your valuable inputs are required to improve the quality"