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 download=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
Comments (0)
Leave a Comment