Writing SystemVerilog Testbenches for beginner

4.5/5 - (4 votes)

Introduction:

This guide serves as a foundational resource for beginners looking to understand SystemVerilog testbench structure. It focuses on a simple Design Under Test (DUT) to provide clear insights into the various components of a testbench. By the end of this guide, users will have a solid understanding of different testbench components and will be capable of writing and comprehending complex testbenches.

Table of Contents

DUT

This DUT(RTL) is simple 8 bit shift register. This module takes four inputs: clock, reset_n, valid_i, and data_i, and provides two outputs: valid_o and data_o. When reset_n is low, indicating a reset condition, it sets the output valid flag to 0 and the output data to 0. Otherwise, it passes the input data and valid flag directly to the output.

Writing SystemVerilog Testbenches For Beginner
DUT(Design Under Test)
Waveform
Timing Diagram

Verification Plan

To verify that the RTL output (data_o) matches the input data (data_i) when the valid flag (valid_i) is asserted. With this we can ensure that the DUT correctly processes the input data and generates the expected output data when the valid flag is asserted.

Stimulus Generation:

  • Generate random input data (data_i) along with the valid flag (valid_i).
  • Ensure that the valid flag is asserted when valid data is provided.

Testbench Setup

The SystemVerilog testbench structure typically consists of several key components and follows a hierarchical organization. Here is an overview of the key elements and their structure:

  • Top Level: Testbench module interacting with the design under test (DUT).
  • Test Cases: Define specific verification scenarios. This is program block.
  • Environment: Contains Drivers, Monitors and Scoreboards.
  • Interface: An interface is a bundle of signals or nets through which a testbench communicates with a design.
  • Driver: The driver is responsible for driving data into the input interfaces of the DUT. It converts transaction-level data into physical signals that are applied to the DUT’s. inputs.
  • Monitors: The monitor captures and monitors the signals and transactions on the output interfaces of the DUT.
  • Scoreboard: Compares expected vs. actual results.

Testbench Structure

 

SV testbench
Simple Testbench Structure
  • Driver and Monitor are connected to DUT through ‘interface’.
  • The scoreboard receives data input to the DUT (expected data or reference data) and data output from the DUT (received data) through mailbox from the Driver and Monitor respectively.
  • The ‘interface’ and ‘mailbox handles’ are connected to their respective blocks by passing the handles as arguments in the new() function.

DUT Code

				
					

module dut(
    input        clock,    // Clock input
    input        reset_n,  // Active-low reset input
    input        valid_i,  // Input valid flag
    input  [7:0] data_i,   // 8-bit input data
    output       valid_o,  // Output valid flag
    output [7:0] data_o    // 8-bit output data
  );
 
  reg       valid_o;
  reg [7:0] data_o;

  always @ (posedge clock or negedge reset_n) 
  begin
    if(!reset_n) 
    begin
     valid_o <= 1'b0;
     data_o  <= 8'h0;
    end
    else
    begin
     data_o  <= data_i;
     valid_o <= valid_i;
    end
  end
endmodule
				
			

Testbench Code

Interface

[Read more about clocking block]

				
					//-------------------------------------------
// interface
//-------------------------------------------
interface dut_interface(input bit clock, reset_n);
    logic       valid_i;
    logic [7:0] data_i;
    logic       valid_o;
    logic [7:0] data_o;

    clocking cb@(posedge clock);
     default input #1 output #1;
     output  valid_i;
     output  data_i;
     input  valid_o;
     input  data_o;
   endclocking

endinterface
				
			

Driver

				
					//-------------------------------------------
// driver
//-------------------------------------------
class driver;
   bit     [7:0] data;
   mailbox #(bit [7:0])  mbx;
   virtual dut_interface vif;

   function new(virtual dut_interface vif, mailbox #(bit [7:0]) mbx);
     this.vif = vif;
     this.mbx = mbx;
     $display("%5t: driver: new()", $realtime);
   endfunction 

   task start();
     $display("%5t: driver: start()", $realtime);
     for(int itr=0; itr<10; itr++)
     begin
      std::randomize(data);
      mbx.put(data);
      vif.cb.valid_i <= 1'b1;
      vif.cb.data_i  <= data;
      @(vif.cb);
     end
   endtask:start
endclass:driver
				
			

 Monitor

				
					//-------------------------------------------
// monitor
//-------------------------------------------
class monitor;
   bit     [7:0] data;
   mailbox #(bit [7:0])  mbx;
   virtual dut_interface vif;

   function new(virtual dut_interface vif, mailbox #(bit [7:0]) mbx);
     this.vif = vif;
     this.mbx = mbx;
     $display("%5t: monitor: new()", $realtime);
   endfunction 

   task start();
     $display("%5t: monitor: start()", $realtime);
     forever
     begin
      @(vif.cb);
      if(vif.cb.valid_o==1'b1)
      begin
       data = vif.cb.data_o;
       mbx.put(data);
      end
     end
   endtask:start
endclass:monitor
				
			

 Scoreboard

				
					//-------------------------------------------
// scoreboard
//-------------------------------------------
class scoreboard;
   int                   err_cnt;
   bit     [7:0]         rcv_data;
   bit     [7:0]         drv_data;
   mailbox #(bit [7:0])  mon2sb;
   mailbox #(bit [7:0])  drv2sb;

   function new(mailbox #(bit [7:0]) mon2sb, drv2sb);
     this.mon2sb = mon2sb;
     this.drv2sb = drv2sb;
     $display("%5t: scorebooard: new()", $realtime);
   endfunction 

   task start();
     $display("%5t: scorebooard: start()", $realtime);
     forever
     begin
      mon2sb.get(rcv_data);
      $display("%5t: scorebooard: Scoreboard received data from monitor (0x%x)", $realtime, rcv_data);
      drv2sb.get(drv_data);
      if(rcv_data == drv_data) begin
       $display("%5t: scoreboardd: Data Matched (0x%x)", $realtime, rcv_data);
      end
      else begin
       err_cnt++;
       $display("%5t: scoreboardd: Error: Data MisMatched expected=0x%x, receved=0x%x", $realtime, drv_data, rcv_data);
      end
     end
   endtask:start

endclass:scoreboard
				
			

 Environment

				
					//-------------------------------------------
// environment
//-------------------------------------------
class environment;
   driver                drv;
   monitor               mon;
   scoreboard            sb;
   mailbox #(bit [7:0])  mon2sb;
   mailbox #(bit [7:0])  drv2sb;
   virtual dut_interface vif;

   function new(virtual dut_interface vif);
     this.vif = vif;

     mon2sb = new();
     drv2sb = new();
     sb  = new(mon2sb, drv2sb);
     drv = new(vif,    drv2sb);
     mon = new(vif,    mon2sb);
     $display("%5t: environment: new()", $realtime);
   endfunction 

   task start();
     $display("%5t: environment: start()", $realtime);
     fork
       sb.start();
       drv.start();
       mon.start();
     join_any
     $display("%5t: environment: start() - completed", $realtime);
   endtask:start


endclass:environment
				
			

 Testcase

				
					//-------------------------------------------
// testcase
//-------------------------------------------
program testcase(dut_interface vif);
  environment env;

  initial begin
   $display("%5t: testcase: initial", $realtime);
    
   env = new(vif);
  
   wait(vif.reset_n == 1'b1);
   repeat(5) @ (posedge vif.clock);

   env.start();

   repeat(10) @ (posedge vif.clock);
   $finish;   
  end
endprogram
				
			

TB Top

				
					//-------------------------------------------
// tb_top
//-------------------------------------------
module tb_top();

  bit  clock;
  bit  reset_n;

  dut_interface intf(clock, reset_n);

  testcase tc(intf);

  dut dut_i(
    .clock    (intf.clock),
    .reset_n  (intf.reset_n),
    .valid_i  (intf.valid_i),
    .data_i   (intf.data_i),
    .valid_o  (intf.valid_o),
    .data_o   (intf.data_o)
   );

  always #5 clock = !clock;

  initial
  begin

    intf.valid_i <= 0;
    intf.data_i  <= 0;

    reset_n <= 1'b0;
    repeat(5) @ (posedge clock);
    reset_n <= 1'b1;
  end

endmodule
				
			

Assignment

  1. Change the stimulus and randomly toggle valid_i signal.  
  2. Enhance the DUT, introduce a pipeline delay so that data requires multiple clock cycles to exit the DUT. Additionally, implement a queue within the scoreboard to store expected data before performing comparisons.

Code Download

DUT_CODE 

TB_CODE

Leave a Comment

Your email address will not be published. Required fields are marked *