Complete SystemVerilog Testbench for Counter - Step-by-Step Guide

Build a complete SystemVerilog testbench for a 4-bit loadable up/down counter. This guide covers all components: DUT, interface, environment, monitors, reference model, scoreboard, assertions, and build automation.

1. Design Under Test (DUT)

Counter Specification

  • 4-bit loadable up/down counter
  • Synchronous reset
  • updown=1: count up, updown=0: count down
  • load=1: load data value into counter

counter.v

module counter(clk, rst, data, updown, load, data_out);

  input clk, rst, load;
  input updown;
  input [3:0] data;
  output reg [3:0] data_out;

  always @(posedge clk) begin
    if (rst)
      data_out <= 4'b0;
    else if (load)
      data_out <= data;
    else
      data_out <= (updown) ? (data_out + 1'b1) : (data_out - 1'b1);
  end

endmodule

2. Interface Definition

The interface encapsulates all signals with clocking blocks for proper synchronization.

counter_if.sv

interface counter_if(input logic clk);

  logic rst, updown, load;
  logic [3:0] data;
  logic [3:0] data_out;

  // Write BFM clocking block (driver)
  clocking wr_cb @(posedge clk);
    output load, updown, rst;
    output data;
  endclocking

  // Write Monitor clocking block
  clocking wrmon_cb @(posedge clk);
    input data;
    input load, rst, updown;
  endclocking

  // Read Monitor clocking block
  clocking rdmon_cb @(posedge clk);
    input data_out;
  endclocking

  // Modports for each component
  modport WR_BFM(clocking wr_cb);
  modport WR_MON(clocking wrmon_cb);
  modport RD_MON(clocking rdmon_cb);

endinterface: counter_if

3. Transaction Class

counter_trans.sv

class counter_trans;
  rand logic rst;
  rand logic load;
  rand logic updown;
  rand logic [3:0] data;
  logic [3:0] data_out;

  constraint c1 { rst dist {0:=95, 1:=5}; }
  constraint c2 { load dist {0:=70, 1:=30}; }

  function void display(string tag);
    $display("[%s] rst=%b load=%b updown=%b data=%h data_out=%h",
             tag, rst, load, updown, data, data_out);
  endfunction

  function bit compare(counter_trans t);
    return (this.data_out == t.data_out);
  endfunction
endclass

4. Generator

counter_gen.sv

class counter_gen;
  counter_trans trans_h;
  mailbox #(counter_trans) gen2wr;

  function new(mailbox #(counter_trans) gen2wr);
    this.gen2wr = gen2wr;
    trans_h = new();
  endfunction

  task start();
    fork
      for (int i = 0; i < no_of_transaction; i++) begin
        counter_trans t = new trans_h;
        assert(t.randomize());
        t.display("GENERATOR");
        gen2wr.put(t);
      end
    join_none
  endtask
endclass

5. Write BFM (Driver)

counter_wr_bfm.sv

class counter_wr_bfm;
  virtual counter_if.WR_BFM wr_if;
  mailbox #(counter_trans) gen2wr;
  counter_trans trans_h;

  function new(virtual counter_if.WR_BFM wr_if,
               mailbox #(counter_trans) gen2wr);
    this.wr_if = wr_if;
    this.gen2wr = gen2wr;
  endfunction

  task drive();
    gen2wr.get(trans_h);
    @(wr_if.wr_cb);
    wr_if.wr_cb.rst    <= trans_h.rst;
    wr_if.wr_cb.load   <= trans_h.load;
    wr_if.wr_cb.updown <= trans_h.updown;
    wr_if.wr_cb.data   <= trans_h.data;
  endtask

  task start();
    fork
      forever drive();
    join_none
  endtask
endclass

6. Read Monitor

counter_rd_mon.sv

class counter_rd_mon;
  virtual counter_if.RD_MON rdmon_if;
  mailbox #(counter_trans) rdmon2sb;
  counter_trans trans_h;

  function new(virtual counter_if.RD_MON rdmon_if,
               mailbox #(counter_trans) rdmon2sb);
    this.rdmon_if = rdmon_if;
    this.rdmon2sb = rdmon2sb;
    trans_h = new;
  endfunction

  task monitor();
    @(rdmon_if.rdmon_cb);
    trans_h.data_out = rdmon_if.rdmon_cb.data_out;
    if ($isunknown(rdmon_if.rdmon_cb.data_out))
      trans_h.data_out = 0;
  endtask

  task start();
    fork
      forever begin
        monitor();
        trans_h.display("RD_MON");
        rdmon2sb.put(new trans_h);
      end
    join_none
  endtask
endclass

7. Reference Model

counter_rm.sv

class counter_rm;
  mailbox #(counter_trans) rm2sb, wrmon2rm;
  counter_trans wrmon2rm_h, temp_h;
  int count;

  function new(mailbox #(counter_trans) wrmon2rm,
               mailbox #(counter_trans) rm2sb);
    this.rm2sb = rm2sb;
    this.wrmon2rm = wrmon2rm;
    temp_h = new();
  endfunction

  task model();
    ++count;
    if (count > 1) begin
      temp_h.rst    = wrmon2rm_h.rst;
      temp_h.load   = wrmon2rm_h.load;
      temp_h.updown = wrmon2rm_h.updown;
      temp_h.data   = wrmon2rm_h.data;

      if (wrmon2rm_h.rst)
        temp_h.data_out = 0;
      else if (wrmon2rm_h.load)
        temp_h.data_out = wrmon2rm_h.data;
      else if (wrmon2rm_h.updown)
        temp_h.data_out = ++temp_h.data_out;
      else
        temp_h.data_out = --temp_h.data_out;
    end
  endtask

  task start();
    fork
      forever begin
        wrmon2rm.get(wrmon2rm_h);
        rm2sb.put(temp_h);
        temp_h = new temp_h;
        model();
      end
    join_none
  endtask
endclass

8. Scoreboard with Coverage

counter_sb.sv

class counter_sb;
  mailbox #(counter_trans) rm2sb, rdmon2sb;
  event DONE;
  int count_transaction, data_verified;
  counter_trans cov_h, rcvd_h;

  covergroup counter_cov;
    option.per_instance = 1;
    RST:  coverpoint cov_h.rst     { bins r[] = {0, 1}; }
    LD:   coverpoint cov_h.load    { bins l[] = {0, 1}; }
    UD:   coverpoint cov_h.updown  { bins ud[] = {0, 1}; }
    DATA: coverpoint cov_h.data    { bins d[] = {[0:15]}; }
    DOUT: coverpoint cov_h.data_out { bins dout[] = {[0:15]}; }
    LDxDATA: cross LD, DATA;
    UDxDOUT: cross UD, DOUT;
  endgroup

  function new(mailbox #(counter_trans) rm2sb,
               mailbox #(counter_trans) rdmon2sb);
    this.rm2sb = rm2sb;
    this.rdmon2sb = rdmon2sb;
    counter_cov = new();
  endfunction

  task start;
    fork
      forever begin
        rm2sb.get(rcvd_h);
        cov_h = rcvd_h;
        counter_cov.sample();
        rdmon2sb.get(cov_h);
        check(rcvd_h);
      end
    join_none
  endtask

  task check(counter_trans rcvd_h);
    count_transaction++;
    if (cov_h.compare(rcvd_h)) begin
      counter_cov.sample();
      data_verified++;
    end
    if (count_transaction >= no_of_transaction)
      ->DONE;
  endtask

  function void report;
    $display("------------ SCOREBOARD REPORT ------------");
    $display("Transactions received : %0d", count_transaction);
    $display("Transactions verified : %0d", data_verified);
    $display("-------------------------------------------");
  endfunction
endclass

9. Environment

counter_env.sv

class counter_env;
  virtual counter_if.WR_BFM wr_if;
  virtual counter_if.WR_MON wrmon_if;
  virtual counter_if.RD_MON rdmon_if;

  mailbox #(counter_trans) gen2wr   = new;
  mailbox #(counter_trans) wrmon2rm = new;
  mailbox #(counter_trans) rm2sb    = new;
  mailbox #(counter_trans) rdmon2sb = new;

  counter_gen    gen_h;
  counter_wr_bfm wr_h;
  counter_wr_mon wrmon_h;
  counter_rd_mon rdmon_h;
  counter_rm     rm_h;
  counter_sb     sb_h;

  function new(virtual counter_if.WR_BFM wr_if,
               virtual counter_if.WR_MON wrmon_if,
               virtual counter_if.RD_MON rdmon_if);
    this.wr_if    = wr_if;
    this.wrmon_if = wrmon_if;
    this.rdmon_if = rdmon_if;
  endfunction

  task build();
    gen_h   = new(gen2wr);
    wr_h    = new(wr_if, gen2wr);
    wrmon_h = new(wrmon_if, wrmon2rm);
    rdmon_h = new(rdmon_if, rdmon2sb);
    rm_h    = new(wrmon2rm, rm2sb);
    sb_h    = new(rm2sb, rdmon2sb);
  endtask

  task reset();
    @(wr_if.wr_cb);
    wr_if.wr_cb.rst <= 1;
    repeat(5) @(wr_if.wr_cb);
    wr_if.wr_cb.rst <= 0;
  endtask

  task start();
    gen_h.start(); wr_h.start(); wrmon_h.start();
    rdmon_h.start(); rm_h.start(); sb_h.start();
  endtask

  task stop();
    wait(sb_h.DONE.triggered);
  endtask

  task run();
    reset(); start(); stop(); sb_h.report();
  endtask
endclass

10. Top Module

top.sv

`include "test.sv"

module top;
  reg clk;
  counter_if intf(clk);

  // DUT
  counter DUV(
    .clk(clk), .rst(intf.rst), .load(intf.load),
    .updown(intf.updown), .data(intf.data), .data_out(intf.data_out)
  );

  // Bind assertions
  bind DUV counter_assertion C_A(
    .clk(clk), .rst(intf.rst), .load(intf.load),
    .updown(intf.updown), .data(intf.data), .count(intf.data_out)
  );

  test test_h;

  initial begin
    test_h = new(intf, intf, intf);
    test_h.build_and_run();
  end

  initial begin
    clk = 0;
    forever #10 clk = ~clk;
  end
endmodule

11. SVA Assertions

counter_assertion.sv

module counter_assertion(clk, rst, data, updown, load, count);
  input logic clk, rst, updown, load;
  input logic [3:0] data, count;

  // Reset clears counter
  property reset_prpty;
    @(posedge clk) rst |=> (count == 4'b0);
  endproperty

  // Up count
  property up_count_prpty;
    @(posedge clk) disable iff(rst)
    (!load && updown) |=> (count == ($past(count) + 1));
  endproperty

  // Down count
  property down_count_prpty;
    @(posedge clk) disable iff(rst)
    (!load && !updown) |=> (count == ($past(count) - 1));
  endproperty

  // Overflow: F -> 0
  property overflow_prpty;
    @(posedge clk) disable iff(rst)
    (!load && updown && count == 4'hF) |=> (count == 4'h0);
  endproperty

  // Underflow: 0 -> F
  property underflow_prpty;
    @(posedge clk) disable iff(rst)
    (!load && !updown && count == 4'h0) |=> (count == 4'hF);
  endproperty

  // Load data
  property load_prpty;
    @(posedge clk) disable iff(rst)
    load |=> (count == $past(data));
  endproperty

  RST:        assert property (reset_prpty);
  UP_COUNT:   assert property (up_count_prpty);
  DOWN_COUNT: assert property (down_count_prpty);
  OVERFLOW:   assert property (overflow_prpty);
  UNDERFLOW:  assert property (underflow_prpty);
  LOAD:       assert property (load_prpty);
endmodule

12. Package File

counter_pkg.sv

package counter_pkg;
  int no_of_transaction;

  `include "counter_trans.sv"
  `include "counter_gen.sv"
  `include "counter_wr_bfm.sv"
  `include "counter_wr_mon.sv"
  `include "counter_rd_mon.sv"
  `include "counter_rm.sv"
  `include "counter_sb.sv"
  `include "counter_env.sv"
endpackage

13. Makefile

RTL     = ../rtl/counter.v
TB      = counter_if.sv counter_assertion.sv counter_pkg.sv top.sv
INC     = +incdir+. +incdir+../test
COVOPT  = -coveropt 3 +cover +acc

# Compile
compile:
	vlib work
	vlog $(COVOPT) $(RTL) $(TB) $(INC)

# Run test
run: compile
	vsim -c -coverage work.top +TEST1 \
	  -do "coverage save -onexit counter_cov; run -all; exit"
	vcover report -html counter_cov

# GUI mode
gui: compile
	vsim -coverage work.top +TEST1

clean:
	rm -rf work transcript *.wlf counter_cov* covhtmlreport

Architecture Diagram

flowchart TB
    subgraph TB["Testbench"]
        GEN[Generator] -->|trans| WR_BFM[Write BFM]
        WR_BFM -->|drive| IF[Interface]
        IF -->|monitor| WR_MON[Write Monitor]
        IF -->|monitor| RD_MON[Read Monitor]
        WR_MON -->|trans| RM[Reference Model]
        RM -->|expected| SB[Scoreboard]
        RD_MON -->|actual| SB
    end
    
    IF <-->|signals| DUT[Counter DUT]
    DUT -.->|bind| ASSERT[SVA Assertions]
    
    style GEN fill:#d1fae5,stroke:#10b981
    style SB fill:#dbeafe,stroke:#3b82f6
    style DUT fill:#fef3c7,stroke:#f59e0b
    style ASSERT fill:#fee2e2,stroke:#ef4444

Running the Testbench

# Compile and run
make run

# View coverage report
firefox covhtmlreport/index.html

# Run in GUI
make gui

Key Takeaways

  • Interfaces with clocking blocks ensure proper signal synchronization
  • Mailboxes provide TLM communication between components
  • Reference Model computes expected values for comparison
  • Covergroups track functional coverage with cross coverage
  • SVA Assertions verify protocol properties concurrently
  • Makefile automates build, run, and coverage reporting
Author
Mayur Kubavat
VLSI Design and Verification Engineer sharing knowledge about SystemVerilog, UVM, and hardware verification methodologies.

Comments (0)

Leave a Comment