SystemVerilog TestBench Example 01

Memory Model TestBench Without Monitor, Agent, and Scoreboard

TestBench Architecture

SystemVerilog TestBench
SystemVerilog TestBench

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 a 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 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;  

  //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 a 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 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
  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 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 the 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 a 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 a drive task to drive the transaction packet to the 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 a 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 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;
  
  //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 the 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 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;
  
  //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.

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(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 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, 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 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
  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.

Click to execute on   

❮ Previous

SystemVerilog TestBench Example – Memory

Memory Model TestBench With Monitor and Scoreboard

TestBench Architecture:

SystemVerilog TestBench
SystemVerilog TestBench

Only monitor and scoreboard are explained here, Refer to ‘Memory Model’ TestBench Without Monitor, Agent, and Scoreboard for other components.

Monitor

  • Samples the interface signals and converts the signal level activity to the transaction level
  • Send the sampled transaction to Scoreboard via Mailbox
  • Below are the steps to write a monitor

1. Writing monitor class.

class monitor;
  ------
endclass

2. Declare interface and mailbox, Get the interface and mailbox handle through the constructor.

  //creating virtual interface handle
  virtual mem_intf mem_vif;
  
  //creating mailbox handle
  mailbox mon2scb;
  
  //constructor
  function new(virtual intf vif,mailbox mon2scb);
    //getting the interface
    this.vif = vif;
    //getting the mailbox handles from  environment 
    this.mon2scb = mon2scb;
  endfunction

3. Sampling logic and sending the sampled transaction to the scoreboard

  task main;
    forever begin
      transaction trans;
      trans = new();

      @(posedge mem_vif.MONITOR.clk);
      wait(`MON_IF.rd_en || `MON_IF.wr_en);
        trans.addr  = `MON_IF.addr;
        trans.wr_en = `MON_IF.wr_en;
        trans.wdata = `MON_IF.wdata;
        if(`MON_IF.rd_en) begin
          trans.rd_en = `MON_IF.rd_en;
          @(posedge mem_vif.MONITOR.clk);
          @(posedge mem_vif.MONITOR.clk);
          trans.rdata = `MON_IF.rdata;
        end      
        mon2scb.put(trans);
    end
  endtask

4. Complete monitor code.

`define MON_IF mem_vif.MONITOR.monitor_cb
class monitor;
  
  //creating virtual interface handle
  virtual mem_intf mem_vif;
  
  //creating mailbox handle
  mailbox mon2scb;
  
  //constructor
  function new(virtual mem_intf mem_vif,mailbox mon2scb);
    //getting the interface
    this.mem_vif = mem_vif;
    //getting the mailbox handles from  environment 
    this.mon2scb = mon2scb;
  endfunction
  
  //Samples the interface signal and send the sample packet to scoreboard
  task main;
    forever begin
      transaction trans;
      trans = new();

      @(posedge mem_vif.MONITOR.clk);
      wait(`MON_IF.rd_en || `MON_IF.wr_en);
        trans.addr  = `MON_IF.addr;
        trans.wr_en = `MON_IF.wr_en;
        trans.wdata = `MON_IF.wdata;
        if(`MON_IF.rd_en) begin
          trans.rd_en = `MON_IF.rd_en;
          @(posedge mem_vif.MONITOR.clk);
          @(posedge mem_vif.MONITOR.clk);
          trans.rdata = `MON_IF.rdata;
        end      
        mon2scb.put(trans);
    end
  endtask
  
endclass

Scoreboard

Scoreboard receives the sampled packet from monitor,

  • if the transaction type is “read”, compares the read data with the local memory data
  • if the transaction type is “write”, local memory will be written with the wdata
class scoreboard;

  ------  
  
endclass

1. Declaring the mailbox and variable to keep count of transactions, connecting handle through the constructor,

  //creating mailbox handle
  mailbox mon2scb;
  
  //used to count the number of transactions
  int no_transactions;
  
  //constructor
  function new(mailbox mon2scb);
    //getting the mailbox handles from  environment 
    this.mon2scb = mon2scb;
  endfunction

2. logic to store wdata and compare rdata with stored data,

   //stores wdata and compare rdata with stored data
  task main;
    transaction trans;
    forever begin
      #50;
      mon2scb.get(trans);
      if(trans.rd_en) begin
        if(mem[trans.addr] != trans.rdata) 
          $error("[SCB-FAIL] Addr = %0h,\n \t   Data :: Expected = %0h Actual = %0h",trans.addr,mem[trans.addr],trans.rdata);
        else 
          $display("[SCB-PASS] Addr = %0h,\n \t   Data :: Expected = %0h Actual = %0h",trans.addr,mem[trans.addr],trans.rdata);
      end
      else if(trans.wr_en)
        mem[trans.addr] = trans.wdata;

      no_transactions++;
    end
  endtask

3. Complete scoreboard code.

class scoreboard;
   
  //creating mailbox handle
  mailbox mon2scb;
  
  //used to count the number of transactions
  int no_transactions;
  
  //array to use as local memory
  bit [7:0] mem[4];
  
  //constructor
  function new(mailbox mon2scb);
    //getting the mailbox handles from  environment 
    this.mon2scb = mon2scb;
    foreach(mem[i]) mem[i] = 8'hFF;
  endfunction
  
  //stores wdata and compare rdata with stored data
  task main;
    transaction trans;
    forever begin
      #50;
      mon2scb.get(trans);
      if(trans.rd_en) begin
        if(mem[trans.addr] != trans.rdata) 
          $error("[SCB-FAIL] Addr = %0h,\n \t   Data :: Expected = %0h Actual = %0h",trans.addr,mem[trans.addr],trans.rdata);
        else 
          $display("[SCB-PASS] Addr = %0h,\n \t   Data :: Expected = %0h Actual = %0h",trans.addr,mem[trans.addr],trans.rdata);
      end
      else if(trans.wr_en)
        mem[trans.addr] = trans.wdata;

      no_transactions++;
    end
  endtask
  
endclass

Environment

Here only updates are mentioned. i.e adding monitor and scoreboard to the previous example.

1. Declare the handles,

  //generator and driver instance
  generator  gen;
  driver     driv;
  monitor    mon;    //---NEW CODE---
  scoreboard scb;    //---NEW CODE---
  
  //mailbox handle's
  mailbox gen2driv;
  mailbox mon2scb;   //---NEW CODE---
  
  //virtual interface
  virtual mem_intf mem_vif;

2. In Construct Method, Create

  • Mailbox (mon2scb)
  • Monitor
  • Scoreboard

and pass the interface handle through the 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();
    mon2scb  = new();
    
    //creating generator and driver
    gen  = new(gen2driv,gen_ended);
    driv = new(mem_vif,gen2driv);
    mon  = new(mem_vif,mon2scb);
    scb  = new(mon2scb);
  endfunction

3. Calling monitor and scoreboard tasks,

  task pre_test();
    driv.reset();
  endtask
  
  task test();
    fork 
      gen.main();
      driv.main();
      mon.main();   //---NEW CODE---
      scb.main();   //---NEW CODE---
    join_any
  endtask
  
  task post_test();
    wait(gen.ended.triggered);
    wait(gen.repeat_count == driv.no_transactions);
    wait(gen.repeat_count == scb.no_transactions);   //---NEW CODE---
  endtask 

4. Complete environment class code,

class environment;
  
  //generator and driver instance
  generator  gen;
  driver     driv;
  monitor    mon;
  scoreboard scb;
  
  //mailbox handle's
  mailbox gen2driv;
  mailbox mon2scb;
  
  //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();
    mon2scb  = new();
    
    //creating generator and driver
    gen  = new(gen2driv,gen_ended);
    driv = new(mem_vif,gen2driv);
    mon  = new(mem_vif,mon2scb);
    scb  = new(mon2scb);
  endfunction
  
  //
  task pre_test();
    driv.reset();
  endtask
  
  task test();
    fork 
    gen.main();
    driv.main();
    mon.main();
    scb.main();      
    join_any
  endtask
  
  task post_test();
    wait(gen_ended.triggered);
    wait(gen.repeat_count == driv.no_transactions);
    wait(gen.repeat_count == scb.no_transactions);
  endtask  
  
  //run task
  task run;
    pre_test();
    test();
    post_test();
    $finish;
  endtask
  
endclass

Edit and Execute Memory Model TestBench code in EDA Playground.

Click to execute on   

❮ Previous

SystemVerilog TestBench Example — Memory_M

SystemVerilog Verification Environment/TestBench for Memory Model

The steps involved in the verification process are,

  • Creation of Verification plan
  • Testbench Architecture
  • Writing TestBench

Before writing/creating the verification plan need to know about design, so will go through the design specification.

* In this example Design/DUT is Memory Model.

Memory Model Design Specification

SystemVerilog Memory Model Design block diagram
SystemVerilog Memory Model Design

Signal Definition:

Signal Name Direction wrt to Design Description
clk input clock signal
reset input reset signal
addr[1:0] input Address signal on which the address is specified
wr_en input write enable signal, indicates the write operation
rd_en input read enable signal, indicates the read operation
wdata[7:0] input wdata signal for write data
rdata[7:0] output rdata signal for read data

Operations:

Write Operation:
address, wr_en, and wdata should be driven at the same clock cycle.

Read Operation:
address and rd_en should be driven on the same clock cycle, Design will respond with the data in the next clock cycle.

Design Features,

  • The Memory model is capable of storing 8bits of data per address location
  • Reset values of each address memory location is ‘hFF

Creation of Verification plan

The verification plan is the list of scenarios need to be verified.

let’s list the few scenarios,

  1. Write and Read to a particular memory location
    • Perform write to any memory location, read from the same memory location, read data should be the same as written data
  2. Write and Read to all memory locations
    • Perform write and read to all the memory locations (as the address is 2bit width the possible address are 2‘b00, 2’b01, 2’b10, and 2’b11)
  3. Default memory value check
    • Check default memory values. (before writing any locations, do read operation we should get default values as ‘hFF)
  4. Reset in Middle of Write/Read Operation
    • Assert reset in between write/read operation and check for default values. (after writing to few locations assert the reset and perform read operation, we should get default memory location value ‘hFF)

TestBench Hierarchy and Architecture

SystemVerilog testbench hierarchy to verify Memory Model
SystemVerilog testbench hierarchy to verify “Memory Model”
SystemVerilog Testbench block diagram
SystemVerilog Testbench block diagram

Writing Verification Environment/TestBench

For simplicity will write the two Testbenches,

Next ❯

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

SystemVerilog TestBench Example – with Scb

‘ADDER’ TestBench With Monitor and Scoreboard

TestBench Architecture

SystemVerilog TestBench Scoreboard block diagram
SystemVerilog TestBench

Only monitor and scoreboard are explained here, Refer to ‘ADDER’ TestBench Without Monitor, Agent, and Scoreboard for other components.

Monitor

  • Samples the interface signals and converts the signal level activity to the transaction level.
  • Send the sampled transaction to Scoreboard via Mailbox.
  • Below are the steps to write a monitor.

1. Writing monitor class.

class monitor;
  ------
endclass

2. Declare interface and mailbox, Get the interface and mailbox handle through the constructor.

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

3. Sampling logic and sending the sampled transaction to the scoreboard.

  task main;
    forever begin
      transaction trans;
      trans = new();
      @(posedge vif.clk);
      wait(vif.valid);
      trans.a   = vif.a;
      trans.b   = vif.b;
      @(posedge vif.clk);
      trans.c   = vif.c;
      @(posedge vif.clk);
      mon2scb.put(trans);
      trans.display("[ Monitor ]");
    end
  endtask

4. Complete monitor code.

class monitor;
  
  //creating virtual interface handle
  virtual intf vif;
  
  //creating mailbox handle
  mailbox mon2scb;
  
  //constructor
  function new(virtual intf vif,mailbox mon2scb);
    //getting the interface
    this.vif = vif;
    //getting the mailbox handles from  environment 
    this.mon2scb = mon2scb;
  endfunction
  
  //Samples the interface signal and send the sample packet to scoreboard
  task main;
    forever begin
      transaction trans;
      trans = new();
      @(posedge vif.clk);
      wait(vif.valid);
      trans.a   = vif.a;
      trans.b   = vif.b;
      @(posedge vif.clk);
      trans.c   = vif.c;
      @(posedge vif.clk);
      mon2scb.put(trans);
      trans.display("[ Monitor ]");
    end
  endtask
  
endclass

Scoreboard

Scoreboard receives the sampled packet from the monitor and compare with the expected result, an error will be reported if the comparison results in a mismatch.

class scoreboard;

  ------  
  
endclass

1. Declaring the mailbox and variable to keep count of transactions,  connecting handle through the constructor,

  //creating mailbox handle
  mailbox mon2scb;
  
  //used to count the number of transactions
  int no_transactions;
  
  //constructor
  function new(mailbox mon2scb);
    //getting the mailbox handles from  environment 
    this.mon2scb = mon2scb;
  endfunction

2. logic to compare the received result with the expected result,

Note: As the DUT behavior is simple, a single line is added for generating the expected output.
Complex designs may use the reference model to generate the expected output.

(trans.a+trans.b) == trans.c

  //Compares the Actual result with the expected result
  task main;
    transaction trans;
    forever begin
      mon2scb.get(trans);
        if((trans.a+trans.b) == trans.c)
          $display("Result is as Expected");
        else
          $error("Wrong Result.\n\tExpeced: %0d Actual: %0d",(trans.a+trans.b),trans.c);
        no_transactions++;
      trans.display("[ Scoreboard ]");
    end
  endtask

3. Complete scoreboard code.

class scoreboard;
   
  //creating mailbox handle
  mailbox mon2scb;
  
  //used to count the number of transactions
  int no_transactions;
  
  //constructor
  function new(mailbox mon2scb);
    //getting the mailbox handles from  environment 
    this.mon2scb = mon2scb;
  endfunction
  
  //Compares the Actual result with the expected result
  task main;
    transaction trans;
    forever begin
      mon2scb.get(trans);
        if((trans.a+trans.b) == trans.c)
          $display("Result is as Expected");
        else
          $error("Wrong Result.\n\tExpeced: %0d Actual: %0d",(trans.a+trans.b),trans.c);
        no_transactions++;
      trans.display("[ Scoreboard ]");
    end
  endtask
  
endclass

Environment

Here only updates are mentioned. i.e adding monitor and scoreboard to the previous example.

1. Declare the handles,

  //generator and driver instance
  generator  gen;
  driver     driv;
  monitor    mon;    //---NEW CODE---
  scoreboard scb;    //---NEW CODE---
  
  //mailbox handle's
  mailbox gen2driv;
  mailbox mon2scb;   //---NEW CODE---
  
  //virtual interface
  virtual intf vif;

2. In Construct Method, Create

  • Mailbox (mon2scb)
  • Monitor
  • Scoreboard

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();
    mon2scb  = new();   //---NEW CODE---
    
    //creating generator and driver
    gen  = new(gen2driv);
    driv = new(vif,gen2driv);
    mon  = new(vif,mon2scb);   //---NEW CODE---
    scb  = new(mon2scb);       //---NEW CODE---
  endfunction

3. Calling monitor and scoreboard tasks,

  task pre_test();
    driv.reset();
  endtask
  
  task test();
    fork 
      gen.main();
      driv.main();
      mon.main();   //---NEW CODE---
      scb.main();   //---NEW CODE---
    join_any
  endtask
  
  task post_test();
    wait(gen.ended.triggered);
    wait(gen.repeat_count == driv.no_transactions);
    wait(gen.repeat_count == scb.no_transactions);   //---NEW CODE---
  endtask 

4. Complete environment class code.

`include "transaction.sv"
`include "generator.sv"
`include "driver.sv"
`include "monitor.sv"
`include "scoreboard.sv"
class environment;
  
  //generator and driver instance
  generator  gen;
  driver     driv;
  monitor    mon;
  scoreboard scb;
  
  //mailbox handle's
  mailbox gen2driv;
  mailbox mon2scb;
  
  //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();
    mon2scb  = new();
    
    //creating generator and driver
    gen  = new(gen2driv);
    driv = new(vif,gen2driv);
    mon  = new(vif,mon2scb);
    scb  = new(mon2scb);
  endfunction
  
  //
  task pre_test();
    driv.reset();
  endtask
  
  task test();
    fork 
      gen.main();
      driv.main();
      mon.main();
      scb.main();
    join_any
  endtask
  
  task post_test();
    wait(gen.ended.triggered);
    wait(gen.repeat_count == driv.no_transactions); //Optional
    wait(gen.repeat_count == scb.no_transactions);
  endtask  
  
  //run task
  task run;
    pre_test();
    test();
    post_test();
    $finish;
  endtask
  
endclass

Edit and Execute Simple adder TestBench code in EDA Playground.

Click to execute on   

❮ Previous

SystemVerilog TestBench Example — Adder

Let’s Write the SystemVerilog TestBench for the simple design “ADDER”.
Before writing the SystemVerilog TestBench, we will look into the design specification.

ADDER:
Below is the block diagram of ADDER.

“Adder” Design block diagram

Adder is,

  • fed with the inputs clock, reset, a, b and valid.
  • has output is c.

The valid signal indicates the valid value on the a and b, On valid signal adder will add the a and b, drives the result in the next clock on c.
Adder add/Sum the 4bit values ‘a’ and ‘b’, and drives the result on c in the next clock.

waveform diagram:

“Adder” Waveform

waveform snapshot from EPWave – EDAPlayground

For the simplicity and ease of understanding, let’s write the two TestBecnh’s,

SystemVerilog TestBench

SystemVerilog TestBench Architecture

About TestBench

Testbench or Verification Environment is used to check the functional correctness of the Design Under Test (DUT) by generating and driving a predefined input sequence to a design, capturing the design output and comparing with-respect-to expected output.

Verification environment is a group of class’s performing specific operation. i.e, generating stimulus, driving, monitoring, etc. and those classes will be named based on the operation

TestBench Components

Name Type Description
transaction class Defines the pin level activity generated by agent (to drive to DUT through the driver) or the activity has to be observed by agent (Placeholder for the activity monitored by the monitor on DUT signals)
generator class Generates the stimulus (create and randomize the transaction class) and send it to Driver
driver class Receives the stimulus (transaction) from a generator and drives the packet level data inside the transaction into pin level (to DUT
monitor class Observes pin level activity on interface signals and converts into packet level which is sent to the components such as scoreboard
agent class An agent is a container class, which groups the class’s (generator, driver, and monitor) specific to an interface or protocol
scoreboard class Receives data items from monitors and compares them with expected values.
Expected values can be either golden reference values or generated from the reference model
environment class The environment is a container class for grouping higher level components like agent’s and scoreboard
test program The test is responsible for,

  • Configuring the testbench
  • Initiate the testbench components construction process
  • Initiate the stimulus driving
testbench_top class This is the topmost file, which connects the DUT and TestBench. It consists of DUT, Test and interface instances, the interface connects the DUT and TestBench

TestBench Hierarchy

SystemVerilog TestBench hierarchy
SystemVerilog TestBench hierarchy

TestBench Architecture

SystemVerilog Testbench
SystemVerilog Testbench

SystemVerilog TestBench Examples