Layered TB in SV
1.signal layer:
This layer connects the TestBench to the RTL design. It consists of interface, clocking, and
modport constructs.
2.command layer:
This layer contains lower-level driver and monitor components, as well as the assertions.
This layer provides a transaction-level interface to the layer above and drives the physical
pins via the signal layer.
3.functional layer:
This layer contains higher-level driver and monitor components, as well as the self-checking
structure (scoreboard/tracker).
4.scenario layer:
This layer uses generators to produce streams or sequences of transactions that are applied
to the functional layer. The generators have a set of weights, constraints or scenarios
specified by the test layer. The randomness of constrained-random testing is introduced
within this layer.
5.test layer:
Tests are located in this layer. Test layer can interact with all the layers. This layer allows to
pass directed commands to functional and command layer.
SystemVerilog TestBench Architecture
TESTBENCH ARCHITECTURE in SV
Fig. Testbench architecture
About TestBench
Testbench or Verification Environment is used to check the functional
correctness of the Design Under Test (DUT) by generating and driving a
predefined input sequence to a design, capturing the design output and
comparing with-respect-to expected output.
Verification environment is a group of class’s performing specific operation.
i.e, generating stimulus, driving, monitoring, etc. and those classes will be
named based on the operation
TestBench Components
Name Type Description
Defines the pin level activity generated by agent (to drive to
transaction class
DUT through the driver) or the activity has to be observed
by agent (Placeholder for the activity monitored by the
monitor on DUT signals)
Generates the stimulus (create and randomize the
generator class
transaction class) and send it to Driver
Receives the stimulus (transaction) from a generator and
driver class drives the packet level data inside the transaction into pin
level (to DUT
Observes pin level activity on interface signals and converts
monitor class into packet level which is sent to the components such as
scoreboard
An agent is a container class, which groups the class’s
agent class (generator, driver, and monitor) specific to an interface or
protocol
Receives data items from monitors and compares them with
expected values.
scoreboard class
Expected values can be either golden reference values or
generated from the reference model
The environment is a container class for grouping higher
environment class
level components like agent’s and scoreboard
The test is responsible for,
test program
Configuring the testbench
Initiate the testbench components construction process
Initiate the stimulus driving
This is the topmost file, which connects the DUT and
testbench_top class TestBench. It consists of DUT, Test and interface instances,
the interface connects the DUT and TestBench
TestBench Hierarchy
SystemVerilog TestBench hierarchy
TestBench Architecture
SystemVerilog Testbench
SystemVerilog TestBench Example — Adder
Let’s Write the SystemVerilog TestBench for the simple design “ADDER”.
Before writing the SystemVerilog TestBench, we will look into the design
specification.
ADDER:
Below is the block diagram of ADDER.
“Adder” Design block diagram
Adder is,
fed with the inputs clock, reset, a, b and valid.
has output is c.
The valid signal indicates the valid value on the a and b, On valid signal
adder will add the a and b, drives the result in the next clock on c.
Adder add/Sum the 4bit values ‘a’ and ‘b’, and drives the result on c in the
next clock.
waveform diagram:
“Adder” Waveform
waveform snapshot from EPWave – EDAPlayground
‘ADDER’ TestBench With Monitor and
Scoreboard
Table of Contents
‘ADDER’ TestBench With Monitor and Scoreboard
o TestBench Architecture
o Monitor
o Scoreboard
o Environment
TestBench Architecture
SystemVerilog TestBench
Only monitor and scoreboard are explained here, Refer to ‘ADDER’
TestBench Without Monitor, Agent, and Scoreboard for other components.
Monitor
Samples the interface signals and converts the signal level activity to
the transaction level.
Send the sampled transaction to Scoreboard via Mailbox.
Below are the steps to write a monitor.
1. Writing monitor class.
class monitor;
------
endclass
2. Declare interface and mailbox, Get the interface and mailbox handle
through the constructor.
//creating virtual interface handle
virtual intf vif;
//creating mailbox handle
mailbox mon2scb;
//constructor
function new(virtual intf vif,mailbox mon2scb);
//getting the interface
this.vif = vif;
//getting the mailbox handles from environment
this.mon2scb = mon2scb;
endfunction
3. Sampling logic and sending the sampled transaction to the scoreboard.
task main;
forever begin
transaction trans;
trans = new();
@(posedge vif.clk);
wait(vif.valid);
trans.a = vif.a;
trans.b = vif.b;
@(posedge vif.clk);
trans.c = vif.c;
@(posedge vif.clk);
mon2scb.put(trans);
trans.display("[ Monitor ]");
end
endtask
4. Complete monitor code.
class monitor;
//creating virtual interface handle
virtual intf vif;
//creating mailbox handle
mailbox mon2scb;
//constructor
function new(virtual intf vif,mailbox mon2scb);
//getting the interface
this.vif = vif;
//getting the mailbox handles from environment
this.mon2scb = mon2scb;
endfunction
//Samples the interface signal and send the sample packet to scoreboard
task main;
forever begin
transaction trans;
trans = new();
@(posedge vif.clk);
wait(vif.valid);
trans.a = vif.a;
trans.b = vif.b;
@(posedge vif.clk);
trans.c = vif.c;
@(posedge vif.clk);
mon2scb.put(trans);
trans.display("[ Monitor ]");
end
endtask
endclass
Scoreboard
Scoreboard receives the sampled packet from the monitor and compare
with the expected result, an error will be reported if the comparison results
in a mismatch.
class scoreboard;
------
endclass
1. Declaring the mailbox and variable to keep count of transactions,
connecting handle through the constructor,
//creating mailbox handle
mailbox mon2scb;
//used to count the number of transactions
int no_transactions;
//constructor
function new(mailbox mon2scb);
//getting the mailbox handles from environment
this.mon2scb = mon2scb;
endfunction
2. logic to compare the received result with the expected result,
Note: As the DUT behavior is simple, a single line is added for generating
the expected output.
Complex designs may use the reference model to generate the expected
output.
(trans.a+trans.b) == trans.c
//Compares the Actual result with the expected result
task main;
transaction trans;
forever begin
mon2scb.get(trans);
if((trans.a+trans.b) == trans.c)
$display("Result is as Expected");
else
$error("Wrong Result.\n\tExpeced: %0d Actual: %0d",(trans.a+trans.b),trans.c);
no_transactions++;
trans.display("[ Scoreboard ]");
end
endtask
3. Complete scoreboard code.
class scoreboard;
//creating mailbox handle
mailbox mon2scb;
//used to count the number of transactions
int no_transactions;
//constructor
function new(mailbox mon2scb);
//getting the mailbox handles from environment
this.mon2scb = mon2scb;
endfunction
//Compares the Actual result with the expected result
task main;
transaction trans;
forever begin
mon2scb.get(trans);
if((trans.a+trans.b) == trans.c)
$display("Result is as Expected");
else
$error("Wrong Result.\n\tExpeced: %0d Actual:
%0d",(trans.a+trans.b),trans.c);
no_transactions++;
trans.display("[ Scoreboard ]");
end
endtask
endclass
Environment
Here only updates are mentioned. i.e adding monitor and scoreboard to
the previous example.
1. Declare the handles,
//generator and driver instance
generator gen;
driver driv;
monitor mon; //---NEW CODE---
scoreboard scb; //---NEW CODE---
//mailbox handle's
mailbox gen2driv;
mailbox mon2scb; //---NEW CODE---
//virtual interface
virtual intf vif;
2. In Construct Method, Create
Mailbox (mon2scb)
Monitor
Scoreboard
and pass the interface handle through the new() method.
//constructor
function new(virtual intf vif);
//get the interface from test
this.vif = vif;
//creating the mailbox (Same handle will be shared across generator and driver)
gen2driv = new();
mon2scb = new(); //---NEW CODE---
//creating generator and driver
gen = new(gen2driv);
driv = new(vif,gen2driv);
mon = new(vif,mon2scb); //---NEW CODE---
scb = new(mon2scb); //---NEW CODE---
endfunction
3. Calling monitor and scoreboard tasks,
task pre_test();
driv.reset();
endtask
task test();
fork
gen.main();
driv.main();
mon.main(); //---NEW CODE---
scb.main(); //---NEW CODE---
join_any
endtask
task post_test();
wait(gen.ended.triggered);
wait(gen.repeat_count == driv.no_transactions);
wait(gen.repeat_count == scb.no_transactions); //---NEW CODE---
endtask
4. Complete environment class code.
`include "transaction.sv"
`include "generator.sv"
`include "driver.sv"
`include "monitor.sv"
`include "scoreboard.sv"
class environment;
//generator and driver instance
generator gen;
driver driv;
monitor mon;
scoreboard scb;
//mailbox handle's
mailbox gen2driv;
mailbox mon2scb;
//virtual interface
virtual intf vif;
//constructor
function new(virtual intf vif);
//get the interface from test
this.vif = vif;
//creating the mailbox (Same handle will be shared across generator and driver)
gen2driv = new();
mon2scb = new();
//creating generator and driver
gen = new(gen2driv);
driv = new(vif,gen2driv);
mon = new(vif,mon2scb);
scb = new(mon2scb);
endfunction
//
task pre_test();
driv.reset();
endtask
task test();
fork
gen.main();
driv.main();
mon.main();
scb.main();
join_any
endtask
task post_test();
wait(gen.ended.triggered);
wait(gen.repeat_count == driv.no_transactions); //Optional
wait(gen.repeat_count == scb.no_transactions);
endtask
//run task
task run;
pre_test();
test();
post_test();
$finish;
endtask
endclass