Professional Documents
Culture Documents
Complete SV TestBench
with Hierarchy
Memory Read
Real hardware is static. If your chip is fabricated with 3M gates, it can’t suddenly have 4M
gates just because you need a little boost. In simulation, the RTL design is specified at
compile time and can’t grow or shrink. Likewise, your simulation has a fixed number of
interfaces, specified at compile time, and can’t change at run time.
SystemVerilog classes and objects are dynamic. If you need to send 1000 transactions, just
construct 1000 objects. Need 2000? Just construct more. If your UVM testbench decides
it needs 2 drivers instead of the default of 1, it just creates another driver (or agent) at
runtime.
Since objects are dynamic, they can’t contain interfaces. But an object can contain pointers.
For example, an agent has a pointer (handle/class variable) to the objects for the driver,
monitor, etc. How can you make a pointer to the RTL interface?
class Driver;
virtual colors_ifc v_ifc; // Pointer to RTL instance
Courtesy: www.verificationguide.com
Verification Process
Before writing/creating the verification plan, need to know about design, so will go through
the design specification.
Operations:
Write Operation:
address, wr_en, and wdata should be driven at the same clock cycle.
Read Operation:
address and rd_en should be driven on the same clock cycle, Design will respond with the data in the
next clock cycle.
Design Features,
The Memory model is capable of storing 8bits of data per address location
Reset values of each address memory location is ‘hFF
Memory DUT
module memory
#(
parameter ADDR_WIDTH = 2, //Memory
parameter DATA_WIDTH = 8 reg [DATA_WIDTH-1:0] mem [2**ADDR_WIDTH];
)
( //Reset
input clk, always @(posedge reset)
input reset, for(int i=0;i<2**ADDR_WIDTH;i++) mem[i]=8'hFF;
Perform write to any memory location, read from the same memory location, read data should be the same
as written data
Perform write and read to all the memory locations (as the address is 2bit width the possible address are
2‘b00, 2’b01, 2’b10, and 2’b11)
Check default memory values. (before writing any locations, do read operation we should get default values
as ‘hFF)
Assert reset in between write/read operation and check for default values. (after writing to few locations
assert the reset and perform read operation, we should get default memory location value ‘hFF)
TestBench Without Monitor, Agent, and Scoreboard
Transaction Class
Fields required to generate the stimulus are declared in the transaction class. Transaction class can
also be used as a placeholder for the activity monitored by the monitor on DUT signals. So, the first
step is to declare the Fields‘ in the transaction class
class transaction;
endclass
Generator Class
Generator class is responsible for,
class generator;
//main task, generates(create and randomizes) the packets and puts into mailbox
task main();
trans = new();
if( !trans.randomize() ) $fatal("Gen:: trans randomization failed");
gen2driv.put(trans);
endtask
endclass
Generator Class
class generator;
//constructor
function new(mailbox gen2driv,event ended);
//getting the mailbox handle from env
this.gen2driv = gen2driv;
this.ended = ended;
endfunction
Interface
Will group the signals, specifies the direction (Modport)
endclocking endclocking
Complete Interface
//constructor
function new(virtual mem_intf mem_vif,mailbox gen2driv);
//getting the interface
this.mem_vif = mem_vif;
//getting the mailbox handle from environment
this.gen2driv = gen2driv;
endfunction
Reset Task
For simplicity, define is used to access interface signals.
gen2driv.get(trans);
$display("--------- [DRIVER-TRANSFER: %0d] ---------",no_transactions);
@(posedge mem_vif.DRIVER.clk);
`DRIV_IF.addr <= trans.addr;
if(trans.wr_en) begin
`DRIV_IF.wr_en <= trans.wr_en;
`DRIV_IF.wdata <= trans.wdata;
$display("\tADDR = %0h \tWDATA = %0h",trans.addr,trans.wdata);
@(posedge mem_vif.DRIVER.clk);
end
Drive Task (contd..)
if(trans.rd_en) begin
`DRIV_IF.rd_en <= trans.rd_en;
@(posedge mem_vif.DRIVER.clk);
`DRIV_IF.rd_en <= 0;
@(posedge mem_vif.DRIVER.clk);
trans.rdata = `DRIV_IF.rdata;
$display("\tADDR = %0h \tRDATA = %0h",trans.addr,`DRIV_IF.rdata);
end
$display("-----------------------------------------");
no_transactions++;
end
endtask
class driver;
//mailbox handle's
mailbox gen2driv;
//virtual interface
virtual mem_intf mem_vif;
Environment Constructor
Create Mailbox, Generator, Driver and pass the interface handle through the new() method.
//constructor
function new(virtual mem_intf mem_vif);
//get the interface from test
this.mem_vif = mem_vif;
//creating the mailbox (Same handle will be shared across generator and driver)
gen2driv = new();
task post_test();
task pre_test(); wait(gen_ended.triggered);
driv.reset(); wait(gen.repeat_count == driv.no_transactions);
endtask endtask
task test();
fork task run;
gen.main(); pre_test();
driv.main(); test();
join_any post_test();
endtask $finish;
endtask
`include "transaction.sv"
Complete Environment Code
`include "generator.sv"
`include "driver.sv"
class environment; //creating the mailbox
//(Same handle will be shared
//generator and driver instance //across generator and driver)
generator gen; gen2driv = new();
driver driv; task post_test();
//creating generator and driver wait(gen_ended.triggered);
//mailbox handle's gen = new(gen2driv,gen_ended); wait(gen.repeat_count ==
mailbox gen2driv; driv = new(mem_vif,gen2driv); driv.no_transactions);
endfunction endtask
//event for synchronization
//between generator and test task pre_test(); //run task
event gen_ended; driv.reset(); task run;
endtask pre_test();
//virtual interface test();
virtual mem_intf mem_vif; task test(); post_test();
fork $finish;
//constructor gen.main(); endtask
function new(virtual mem_intf mem_vif); driv.main();
//get the interface from test join_any endclass
this.mem_vif = mem_vif; endtask
Test
Test is written with program construct and is responsible for,
`include "environment.sv"
program test(mem_intf intf);
initial begin
//creating environment
env = new(intf);
//calling run of env, it interns calls generator and driver main tasks.
env.run();
end
endprogram
`include "interface.sv"
TestBench Top
`include "random_test.sv"
endclass
TestBench With Monitor, and Scoreboard
Monitor
Monitor class is responsible for,
Samples the interface signals and converts the signal level activity to the transaction level
//constructor
function new(virtual intf vif,mailbox mon2scb);
//getting the interface
this.vif = vif;
//getting the mailbox handles from environment
this.mon2scb = mon2scb;
endfunction
Sampling logic
task main;
forever begin
transaction trans;
trans = new();
@(posedge mem_vif.MONITOR.clk);
wait(`MON_IF.rd_en || `MON_IF.wr_en);
trans.addr = `MON_IF.addr;
trans.wr_en = `MON_IF.wr_en;
trans.wdata = `MON_IF.wdata;
if(`MON_IF.rd_en) begin
trans.rd_en = `MON_IF.rd_en;
@(posedge mem_vif.MONITOR.clk);
@(posedge mem_vif.MONITOR.clk);
trans.rdata = `MON_IF.rdata;
end
mon2scb.put(trans);
end
endtask
Complete Monitor Code task main;
forever begin
transaction trans;
trans = new();
`define MON_IF mem_vif.MONITOR.monitor_cb
class monitor;
@(posedge mem_vif.MONITOR.clk);
wait(`MON_IF.rd_en || `MON_IF.wr_en);
//creating virtual interface handle
trans.addr = `MON_IF.addr;
virtual mem_intf mem_vif;
trans.wr_en = `MON_IF.wr_en;
trans.wdata = `MON_IF.wdata;
//creating mailbox handle
if(`MON_IF.rd_en) begin
mailbox mon2scb;
trans.rd_en = `MON_IF.rd_en;
@(posedge mem_vif.MONITOR.clk);
//constructor
@(posedge mem_vif.MONITOR.clk);
function new(virtual mem_intf mem_vif,mailbox
trans.rdata = `MON_IF.rdata;
mon2scb);
end
//getting the interface
mon2scb.put(trans);
this.mem_vif = mem_vif;
end
//getting the mailbox handles from environment
endtask
this.mon2scb = mon2scb;
endfunction
endclass
Scoreboard
Scoreboard receives the sampled packet from monitor
If the transaction type is “read”, compares the read data with the local memory data
If the transaction type is “write”, local memory will be written with the wdata
class scoreboard;
//constructor
function new(mailbox mon2scb);
//getting the mailbox handles from environment
this.mon2scb = mon2scb;
endfunction
Store wdata and compare rdata with stored data,
task main;
transaction trans;
forever begin
mon2scb.get(trans);
if(trans.rd_en) begin
if(mem[trans.addr] != trans.rdata)
$error("[SCB-FAIL] Addr = %0h,\n \t Data :: Expected = %0h Actual =
%0h",trans.addr,mem[trans.addr],trans.rdata);
else
$display("[SCB-PASS] Addr = %0h,\n \t Data :: Expected = %0h Actual =
%0h",trans.addr,mem[trans.addr],trans.rdata);
end
else if(trans.wr_en)
mem[trans.addr] = trans.wdata;
no_transactions++;
end
endtask
task main;
transaction trans;
Complete Scoreboard forever begin
mon2scb.get(trans);
if(trans.rd_en) begin
class scoreboard; if(mem[trans.addr] != trans.rdata)
$error("[SCB-FAIL] Addr = %0h,\n \t Data ::
//creating mailbox handle Expected = %0h Actual =
mailbox mon2scb; %0h",trans.addr,mem[trans.addr],trans.rdata);
else
//used to count the number of transactions $display("[SCB-PASS] Addr = %0h,\n \t Data ::
int no_transactions; Expected = %0h Actual =
%0h",trans.addr,mem[trans.addr],trans.rdata);
//array to use as local memory end
bit [7:0] mem[4]; else if(trans.wr_en)
mem[trans.addr] = trans.wdata;
//constructor
function new(mailbox mon2scb); no_transactions++;
//getting the mailbox handles from end
environment endtask
this.mon2scb = mon2scb;
foreach(mem[i]) mem[i] = 8'hFF; endclass
endfunction
`include "transaction.sv" Complete Environment Code
`include "generator.sv"
`include "driver.sv" //creating the mailbox
class environment; //(Same handle will be shared
//across generator and driver)
//generator and driver instance gen2driv = new();
generator gen; mon2scb = new();
driver driv; task post_test();
//creating generator and driver
monitor mon; wait(gen_ended.triggered);
gen = new(gen2driv,gen_ended);
scoreboard scb; wait(gen.repeat_count ==
driv = new(mem_vif,gen2driv);
driv.no_transactions);
mon = new(mem_vif,mon2scb);
//mailbox handle's wait(gen.repeat_count ==
scb = new(mon2scb);
mailbox gen2driv; scb.no_transactions);
endfunction
mailbox mon2scb; endtask
task pre_test();
//event for synchronization //run task
driv.reset();
//between generator and test task run;
endtask
event gen_ended; pre_test();
test();
task test();
//virtual interface post_test();
fork
virtual mem_intf mem_vif; $finish;
gen.main();
endtask
driv.main();
//constructor mon.main();
function new(virtual mem_intf mem_vif); endclass
scb.main();
//get the interface from test join_any
this.mem_vif = mem_vif; endtask
Complete SV Test bench for Adder Example
DUT
module adder(input clk, reset, input [7:0] in1, in2, output reg [8:0] out);
always@(posedge clk or posedge reset) begin
if(reset) out <= 0;
else out <= in1 + in2;
end
endmodule
Adder design produces the resultant addition of two variables on the positive edge of the clock. A
reset signal is used to clear ‘out’ signal to 0.
Note: Adder can be easily developed with combinational logic. A clock and reset are introduced to
have the flavor of a clock and reset in testbench code.
The generator creates or generates randomized transactions or
The transaction is a packet that is stimuli and passes them to the driver.
driven to the DUT or monitored by class generator;
the monitor as a pin-level activity. int count;
mailbox gen_to_drv;
transaction tr;
class transaction;
rand bit [7:0] ip1, ip2; function new(mailbox gen_to_drv);
bit [8:0] out; this.gen_to_drv = gen_to_drv;
endfunction
constraint ip_c {ip1 < 100; ip2 < 100;}
task run;
endclass repeat(count) begin
tr = new();
tr.randomize();
gen_to_drv.put(tr);
end
endtask
endclass
class driver; class monitor;
virtual add_if vif; virtual add_if vif;
mailbox gen_to_drv; mailbox mon_to_sb;
transaction tr;
function new(mailbox mon_to_sb, virtual add_if vif);
function new(mailbox gen_to_drv, virtual add_if this.vif = vif;
vif); this.mon_to_sb = mon_to_sb;
this.gen_to_drv = gen_to_drv; endfunction
this.vif = vif;
endfunction task run;
forever begin
task run; transaction mon_tr;
forever begin wait(!vif.reset);
// Driver to the DUT @(posedge vif.clk);
@(posedge vif.clk); mon_tr = new();
gen_to_drv.get(tr); mon_tr.ip1 = vif.ip1;
mon_tr.ip2 = vif.ip2;
vif.ip1 <= tr.ip1; @(posedge vif.clk);
vif.ip2 <= tr.ip2; mon_tr.out = vif.out;
@(posedge vif.clk); mon_to_sb.put(mon_tr);
tr.out <= vif.out; end
end endtask
endtask endclass
endclass
class agent; class scoreboard;
driver drv; int compare_cnt;
monitor mon; mailbox mon_to_sb;
generator gen;
function new(mailbox mon_to_sb);
mailbox gen_to_drv, mon_to_sb; this.mon_to_sb = mon_to_sb;
virtual add_if vif; endfunction
mailbox mon_to_sb;
An agent is a container that holds the generator, driver, and monitor.
function new(virtual add_if vif);
mon_to_sb = new();
agt = new(vif, mon_to_sb); The scoreboard receives the transaction packet from the monitor and
sb = new(mon_to_sb); compares it with the reference model.
endfunction An environment allows a well-mannered hierarchy and container
for agents, scoreboards
task run();
fork The test is at the top of program base_test(add_if vif);
agt.run(); the hierarchy that env env_o;
sb.run(); initiates the environment
join_any component construction initial begin
wait(agt.gen.count == and connection between env_o = new(vif);
sb.compare_cnt); them. env_o.agt.gen.count = 5;
$finish; env_o.run();
endtask end
endclass endprogram
Test Bench Top
The testbench top is a top-level component that includes interface and DUT instances. It connects
design with the testbench.
module tb_top;
bit clk;
bit reset;
always #2 clk = ~clk;
initial begin
clk = 0;
reset = 1;
#5;
reset = 0;
end
endmodule