Here is an example of how a SystemVerilog testbench can be constructed to verify functionality of a simple adder. Remember that the goal here is to develop a modular and scalable testbench architecture with all the standard verification components in a testbench.
You can also write Verilog code for testing such simple circuits, but bigger and more complex designs typically require a scalable testbench architecture and this is an example of how to build a scalable testbench. Different designs require different driver, monitor and scoreboard implementation that depends on design specifics.
Design
// An adder is combinational logic and does not
// have a clock
module my_adder (adder_if _if);
always_comb begin
if (_if.rstn) begin
_if.sum <= 0;
_if.carry <= 0;
end else begin
{_if.carry, _if.sum} <= _if.a + _if.b;
end
end
endmodule
Transaction Object
// To verify that the adder adds, we also need to check that it
// does not add when rstn is 0, and hence rstn should also be
// randomized along with a and b.
class Packet;
rand bit rstn;
rand bit[7:0] a;
rand bit[7:0] b;
bit [7:0] sum;
bit carry;
// Print contents of the data packet
function void print(string tag="");
$display ("T=%0t %s a=0x%0h b=0x%0h sum=0x%0h carry=0x%0h", $time, tag, a, b, sum, carry);
endfunction
// This is a utility function to allow copying contents in
// one Packet variable to another.
function void copy(Packet tmp);
this.a = tmp.a;
this.b = tmp.b;
this.rstn = tmp.rstn;
this.sum = tmp.sum;
this.carry = tmp.carry;
endfunction
endclass
Driver
class driver;
virtual adder_if m_adder_vif;
virtual clk_if m_clk_vif;
event drv_done;
mailbox drv_mbx;
task run();
$display ("T=%0t [Driver] starting ...", $time);
// Try to get a new transaction every time and then assign
// packet contents to the interface. But do this only if the
// design is ready to accept new transactions
forever begin
Packet item;
$display ("T=%0t [Driver] waiting for item ...", $time);
drv_mbx.get(item);
@ (posedge m_clk_vif.tb_clk);
item.print("Driver");
m_adder_vif.rstn <= item.rstn;
m_adder_vif.a <= item.a;
m_adder_vif.b <= item.b;
->drv_done;
end
endtask
endclass
Monitor
// The monitor has a virtual interface handle with which it can monitor
// the events happening on the interface. It sees new transactions and then
// captures information into a packet and sends it to the scoreboard
// using another mailbox.
class monitor;
virtual adder_if m_adder_vif;
virtual clk_if m_clk_vif;
mailbox scb_mbx; // Mailbox connected to scoreboard
task run();
$display ("T=%0t [Monitor] starting ...", $time);
// Check forever at every clock edge to see if there is a
// valid transaction and if yes, capture info into a class
// object and send it to the scoreboard when the transaction
// is over.
forever begin
Packet m_pkt = new();
@(posedge m_clk_vif.tb_clk);
#1;
m_pkt.a = m_adder_vif.a;
m_pkt.b = m_adder_vif.b;
m_pkt.rstn = m_adder_vif.rstn;
m_pkt.sum = m_adder_vif.sum;
m_pkt.carry = m_adder_vif.carry;
m_pkt.print("Monitor");
scb_mbx.put(m_pkt);
end
endtask
endclass
Scoreboard
// The scoreboard is responsible to check data integrity. Since the design
// simple adds inputs to give sum and carry, scoreboard helps to check if the
// output has changed for given set of inputs based on expected logic
class scoreboard;
mailbox scb_mbx;
task run();
forever begin
Packet item, ref_item;
scb_mbx.get(item);
item.print("Scoreboard");
// Copy contents from received packet into a new packet so
// just to get a and b.
ref_item = new();
ref_item.copy(item);
// Let us calculate the expected values in carry and sum
if (ref_item.rstn)
{ref_item.carry, ref_item.sum} = ref_item.a + ref_item.b;
else
{ref_item.carry, ref_item.sum} = 0;
// Now, carry and sum outputs in the reference variable can be compared
// with those in the received packet
if (ref_item.carry != item.carry) begin
$display("[%0t] Scoreboard Error! Carry mismatch ref_item=0x%0h item=0x%0h", $time, ref_item.carry, item.carry);
end else begin
$display("[%0t] Scoreboard Pass! Carry match ref_item=0x%0h item=0x%0h", $time, ref_item.carry, item.carry);
end
if (ref_item.sum != item.sum) begin
$display("[%0t] Scoreboard Error! Sum mismatch ref_item=0x%0h item=0x%0h", $time, ref_item.sum, item.sum);
end else begin
$display("[%0t] Scoreboard Pass! Sum match ref_item=0x%0h item=0x%0h", $time, ref_item.sum, item.sum);
end
end
endtask
endclass
Generator
// Sometimes we simply need to generate N random transactions to random
// locations so a generator would be useful to do just that. In this case
// loop determines how many transactions need to be sent
class generator;
int loop = 10;
event drv_done;
mailbox drv_mbx;
task run();
for (int i = 0; i < loop; i++) begin
Packet item = new;
item.randomize();
$display ("T=%0t [Generator] Loop:%0d/%0d create next item", $time, i+1, loop);
drv_mbx.put(item);
$display ("T=%0t [Generator] Wait for driver to be done", $time);
@(drv_done);
end
endtask
endclass
Environment
// Lets say that the environment class was already there, and generator is
// a new component that needs to be included in the ENV.
class env;
generator g0; // Generate transactions
driver d0; // Driver to design
monitor m0; // Monitor from design
scoreboard s0; // Scoreboard connected to monitor
mailbox scb_mbx; // Top level mailbox for SCB <-> MON
virtual adder_if m_adder_vif; // Virtual interface handle
virtual clk_if m_clk_vif; // TB clk
event drv_done;
mailbox drv_mbx;
function new();
d0 = new;
m0 = new;
s0 = new;
scb_mbx = new();
g0 = new;
drv_mbx = new;
endfunction
virtual task run();
// Connect virtual interface handles
d0.m_adder_vif = m_adder_vif;
m0.m_adder_vif = m_adder_vif;
d0.m_clk_vif = m_clk_vif;
m0.m_clk_vif = m_clk_vif;
// Connect mailboxes between each component
d0.drv_mbx = drv_mbx;
g0.drv_mbx = drv_mbx;
m0.scb_mbx = scb_mbx;
s0.scb_mbx = scb_mbx;
// Connect event handles
d0.drv_done = drv_done;
g0.drv_done = drv_done;
// Start all components - a fork join_any is used because
// the stimulus is generated by the generator and we want the
// simulation to exit only when the generator has finished
// creating all transactions. Until then all other components
// have to run in the background.
fork
s0.run();
d0.run();
m0.run();
g0.run();
join_any
endtask
endclass
Test
// The test can instantiate any environment. In this test, we are using
// an environment without the generator and hence the stimulus should be
// written in the test.
class test;
env e0;
mailbox drv_mbx;
function new();
drv_mbx = new();
e0 = new();
endfunction
virtual task run();
e0.d0.drv_mbx = drv_mbx;
e0.run();
endtask
endclass
Interface
// Adder interface contains all signals that the adder requires
// to operate
interface adder_if();
logic rstn;
logic [7:0] a;
logic [7:0] b;
logic [7:0] sum;
logic carry;
endinterface
// Although an adder does not have a clock, let us create a mock clock
// used in the testbench to synchronize when value is driven and when
// value is sampled. Typically combinational logic is used between
// sequential elements like FF in a real circuit. So, let us assume
// that inputs to the adder is provided at some posedge clock. But because
// the design does not have clock in its input, we will keep this clock
// in a separate interface that is available only to testbench components
interface clk_if();
logic tb_clk;
initial tb_clk <= 0;
always #10 tb_clk = ~tb_clk;
endinterface
Testbench Top
module tb;
bit tb_clk;
clk_if m_clk_if ();
adder_if m_adder_if ();
my_adder u0 (m_adder_if);
initial begin
test t0;
t0 = new;
t0.e0.m_adder_vif = m_adder_if;
t0.e0.m_clk_vif = m_clk_if;
t0.run();
// Once the main stimulus is over, wait for some time
// until all transactions are finished and then end
// simulation. Note that $finish is required because
// there are components that are running forever in
// the background like clk, monitor, driver, etc
#50 $finish;
end
endmodule

ncsim> run T=0 [Driver] starting ... T=0 [Driver] waiting for item ... T=0 [Monitor] starting ... T=0 [Generator] Loop:1/5 create next item T=0 [Generator] Wait for driver to be done T=10 Driver a=0x16 b=0x11 sum=0x0 carry=0x0 T=10 [Driver] waiting for item ... T=10 [Generator] Loop:2/5 create next item T=10 [Generator] Wait for driver to be done T=11 Monitor a=0x16 b=0x11 sum=0x0 carry=0x0 T=11 Scoreboard a=0x16 b=0x11 sum=0x0 carry=0x0 [11] Scoreboard Pass! Carry match ref_item=0x0 item=0x0 [11] Scoreboard Pass! Sum match ref_item=0x0 item=0x0 T=30 Driver a=0xde b=0x6 sum=0x0 carry=0x0 T=30 [Driver] waiting for item ... T=30 [Generator] Loop:3/5 create next item T=30 [Generator] Wait for driver to be done T=31 Monitor a=0xde b=0x6 sum=0x0 carry=0x0 T=31 Scoreboard a=0xde b=0x6 sum=0x0 carry=0x0 [31] Scoreboard Pass! Carry match ref_item=0x0 item=0x0 [31] Scoreboard Pass! Sum match ref_item=0x0 item=0x0 T=50 Driver a=0xb1 b=0xbd sum=0x0 carry=0x0 T=50 [Driver] waiting for item ... T=50 [Generator] Loop:4/5 create next item T=50 [Generator] Wait for driver to be done T=51 Monitor a=0xb1 b=0xbd sum=0x0 carry=0x0 T=51 Scoreboard a=0xb1 b=0xbd sum=0x0 carry=0x0 [51] Scoreboard Pass! Carry match ref_item=0x0 item=0x0 [51] Scoreboard Pass! Sum match ref_item=0x0 item=0x0 T=70 Driver a=0x63 b=0xfb sum=0x0 carry=0x0 T=70 [Driver] waiting for item ... T=70 [Generator] Loop:5/5 create next item T=70 [Generator] Wait for driver to be done T=71 Monitor a=0x63 b=0xfb sum=0x5e carry=0x1 T=71 Scoreboard a=0x63 b=0xfb sum=0x5e carry=0x1 [71] Scoreboard Pass! Carry match ref_item=0x1 item=0x1 [71] Scoreboard Pass! Sum match ref_item=0x5e item=0x5e T=90 Driver a=0x71 b=0xbc sum=0x0 carry=0x0 T=90 [Driver] waiting for item ... T=91 Monitor a=0x71 b=0xbc sum=0x0 carry=0x0 T=91 Scoreboard a=0x71 b=0xbc sum=0x0 carry=0x0 [91] Scoreboard Pass! Carry match ref_item=0x0 item=0x0 [91] Scoreboard Pass! Sum match ref_item=0x0 item=0x0 T=111 Monitor a=0x71 b=0xbc sum=0x0 carry=0x0 T=111 Scoreboard a=0x71 b=0xbc sum=0x0 carry=0x0 [111] Scoreboard Pass! Carry match ref_item=0x0 item=0x0 [111] Scoreboard Pass! Sum match ref_item=0x0 item=0x0 T=131 Monitor a=0x71 b=0xbc sum=0x0 carry=0x0 T=131 Scoreboard a=0x71 b=0xbc sum=0x0 carry=0x0 [131] Scoreboard Pass! Carry match ref_item=0x0 item=0x0 [131] Scoreboard Pass! Sum match ref_item=0x0 item=0x0 Simulation complete via $finish(1) at time 140 NS + 0 ./testbench.sv:265 #50 $finish;
Buggy Design
Although the previous simulation showed everything as pass, how do we know if there is a bug in the checker ? Let us introduce a bug in the design to see if the checker fails.
module my_adder (adder_if _if);
always_comb begin
// Let sum and carry be reset when rstn is 1 instead of 0
// A simple but yet possible design bug
if (_if.rstn) begin
_if.sum <= 0;
_if.carry <= 0;
end else begin
{_if.carry, _if.sum} <= _if.a + _if.b;
end
end
endmodule
See that the checker now reports an error which proves that the checker is implemented correctly.


ncsim> run T=0 [Driver] starting ... T=0 [Driver] waiting for item ... T=0 [Monitor] starting ... T=0 [Generator] Loop:1/5 create next item T=0 [Generator] Wait for driver to be done T=10 Driver a=0x16 b=0x11 sum=0x0 carry=0x0 T=10 [Driver] waiting for item ... T=10 [Generator] Loop:2/5 create next item T=10 [Generator] Wait for driver to be done T=11 Monitor a=0x16 b=0x11 sum=0x27 carry=0x0 T=11 Scoreboard a=0x16 b=0x11 sum=0x27 carry=0x0 [11] Scoreboard Pass! Carry match ref_item=0x0 item=0x0 [11] Scoreboard Error! Sum mismatch ref_item=0x0 item=0x27 T=30 Driver a=0xde b=0x6 sum=0x0 carry=0x0 T=30 [Driver] waiting for item ... T=30 [Generator] Loop:3/5 create next item T=30 [Generator] Wait for driver to be done T=31 Monitor a=0xde b=0x6 sum=0xe4 carry=0x0 T=31 Scoreboard a=0xde b=0x6 sum=0xe4 carry=0x0 [31] Scoreboard Pass! Carry match ref_item=0x0 item=0x0 [31] Scoreboard Error! Sum mismatch ref_item=0x0 item=0xe4 T=50 Driver a=0xb1 b=0xbd sum=0x0 carry=0x0 T=50 [Driver] waiting for item ... T=50 [Generator] Loop:4/5 create next item T=50 [Generator] Wait for driver to be done T=51 Monitor a=0xb1 b=0xbd sum=0x6e carry=0x1 T=51 Scoreboard a=0xb1 b=0xbd sum=0x6e carry=0x1 [51] Scoreboard Error! Carry mismatch ref_item=0x0 item=0x1 [51] Scoreboard Error! Sum mismatch ref_item=0x0 item=0x6e T=70 Driver a=0x63 b=0xfb sum=0x0 carry=0x0 T=70 [Driver] waiting for item ... T=70 [Generator] Loop:5/5 create next item T=70 [Generator] Wait for driver to be done T=71 Monitor a=0x63 b=0xfb sum=0x0 carry=0x0 T=71 Scoreboard a=0x63 b=0xfb sum=0x0 carry=0x0 [71] Scoreboard Error! Carry mismatch ref_item=0x1 item=0x0 [71] Scoreboard Error! Sum mismatch ref_item=0x5e item=0x0 T=90 Driver a=0x71 b=0xbc sum=0x0 carry=0x0 T=90 [Driver] waiting for item ... T=91 Monitor a=0x71 b=0xbc sum=0x2d carry=0x1 T=91 Scoreboard a=0x71 b=0xbc sum=0x2d carry=0x1 [91] Scoreboard Error! Carry mismatch ref_item=0x0 item=0x1 [91] Scoreboard Error! Sum mismatch ref_item=0x0 item=0x2d T=111 Monitor a=0x71 b=0xbc sum=0x2d carry=0x1 T=111 Scoreboard a=0x71 b=0xbc sum=0x2d carry=0x1 [111] Scoreboard Error! Carry mismatch ref_item=0x0 item=0x1 [111] Scoreboard Error! Sum mismatch ref_item=0x0 item=0x2d T=131 Monitor a=0x71 b=0xbc sum=0x2d carry=0x1 T=131 Scoreboard a=0x71 b=0xbc sum=0x2d carry=0x1 [131] Scoreboard Error! Carry mismatch ref_item=0x0 item=0x1 [131] Scoreboard Error! Sum mismatch ref_item=0x0 item=0x2d Simulation complete via $finish(1) at time 140 NS + 0 ./testbench.sv:265 #50 $finish;