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