Memory Model TestBench Without Monitor, Agent, and Scoreboard
Table of Contents
TestBench Architecture

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 - Below are the steps to write a transaction class
1. Declaring the fields.
class transaction; //declaring the transaction items bit [1:0] addr; bit wr_en; bit rd_en; bit [7:0] wdata; bit [7:0] rdata; bit [1:0] cnt; endclass
2. To generate the random stimulus, declare the fields as rand.
class transaction;
//declaring the transaction items
rand bit [1:0] addr;
rand bit wr_en;
rand bit rd_en;
rand bit [7:0] wdata;
bit [7:0] rdata;
bit [1:0] cnt;
endclass
3. Either write or read operation will be performed at once, so wr_en or rd_en is generated by adding constraint.
class transaction;
//declaring the transaction items
rand bit [1:0] addr;
rand bit wr_en;
rand bit rd_en;
rand bit [7:0] wdata;
bit [7:0] rdata;
bit [1:0] cnt;
//constaint, to generate any one among write and read
constraint wr_rd_c { wr_en != rd_en; };
endclass
Generator Class
Generator class is responsible for,
- Generating the stimulus by randomizing the transaction class
- Sending the randomized class to driver
class generator; ------ endclass
1. Declare the transaction class handle,
class generator; //declaring transaction class rand transaction trans; endclass
2. Randomize the transaction class,
class generator;
//declaring transaction class
rand transaction trans;
//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
3. Mailbox is used to send the randomized transaction to Driver,
This involves,
- Declaring the Mailbox
- Getting the Mailbox handle from the env class. ( because the same mailbox will be shared across generator and driver)
class generator;
//declaring transaction class
rand transaction trans;
//declaring mailbox
mailbox gen2driv;
//constructor
function new(mailbox gen2driv);
//getting the mailbox handle from env
this.gen2driv = gen2driv;
endfunction
//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
4. Adding a variable to control the number of packets to be created,
class generator;
//declaring transaction class
rand transaction trans;
//declaring mailbox
mailbox gen2driv;
//repeat count, to specify number of items to generate
int repeat_count;
//constructor
function new(mailbox gen2driv);
//getting the mailbox handle from env
this.gen2driv = gen2driv;
endfunction
//main task, generates(create and randomizes) the repeat_count number of transaction packets and puts into mailbox
task main();
repeat(repeat_count) begin
trans = new();
if( !trans.randomize() ) $fatal("Gen:: trans randomization failed");
gen2driv.put(trans);
end
endtask
endclass
5. Adding an event to indicate the completion of the generation process, the event will be triggered on the completion of the Generation process.
class generator;
//declaring transaction class
rand transaction trans;
//declaring mailbox
mailbox gen2driv;
//repeat count, to specify number of items to generate
int repeat_count;
//event
event ended;
//constructor
function new(mailbox gen2driv,event ended);
//getting the mailbox handle from env
this.gen2driv = gen2driv;
this.ended = ended;
endfunction
//main task, generates(create and randomizes) the repeat_count number of transaction packets and puts into mailbox
task main();
repeat(repeat_count) begin
trans = new();
if( !trans.randomize() ) $fatal("Gen:: trans randomization failed");
gen2driv.put(trans);
end
-> ended;
endtask
endclass
Interface:
Interface will group the signals, specifies the direction (Modport) and Synchronize the signals(Clocking Block).
interface mem_intf(input logic clk,reset); ---- endinterface
1. Driver Clocking Block,
//driver clocking block
clocking driver_cb @(posedge clk);
default input #1 output #1;
output addr;
output wr_en;
output rd_en;
output wdata;
input rdata;
endclocking
2. Monitor Clocking Block,
//monitor clocking block
clocking monitor_cb @(posedge clk);
default input #1 output #1;
input addr;
input wr_en;
input rd_en;
input wdata;
input rdata;
endclocking
3. Driver and Monitor modport,
//driver modport modport DRIVER (clocking driver_cb,input clk,reset); //monitor modport modport MONITOR (clocking monitor_cb,input clk,reset);
4. Complete Interface code,
interface mem_intf(input logic clk,reset);
//declaring the signals
logic [1:0] addr;
logic wr_en;
logic rd_en;
logic [7:0] wdata;
logic [7:0] rdata;
//driver clocking block
clocking driver_cb @(posedge clk);
default input #1 output #1;
output addr;
output wr_en;
output rd_en;
output wdata;
input rdata;
endclocking
//monitor clocking block
clocking monitor_cb @(posedge clk);
default input #1 output #1;
input addr;
input wr_en;
input rd_en;
input wdata;
input rdata;
endclocking
//driver modport
modport DRIVER (clocking driver_cb,input clk,reset);
//monitor modport
modport MONITOR (clocking monitor_cb,input clk,reset);
endinterface
Driver Class
Driver class is responsible for,
- receive the stimulus generated from the generator and drive to DUT by assigning transaction class values to interface signals.
class driver; ---- endclass
1. Declare interface and mailbox, Get the interface and mailbox handle through the constructor
//creating virtual interface handle
virtual mem_intf mem_vif;
//creating mailbox handle
mailbox gen2driv;
//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
2. Adding a reset task, which initializes the Interface signals to default values
For simplicity, define is used to access interface signals.
`define DRIV_IF mem_vif.DRIVER.driver_cb
`DRIV_IF will point to mem_vif.DRIVER.driver_cb
//Reset task, Reset the Interface signals to default/initial values
task reset;
wait(mem_vif.reset);
$display("--------- [DRIVER] Reset Started ---------");
`DRIV_IF.wr_en <= 0;
`DRIV_IF.rd_en <= 0;
`DRIV_IF.addr <= 0;
`DRIV_IF.wdata <= 0;
wait(!mem_vif.reset);
$display("--------- [DRIVER] Reset Ended ---------");
endtask
3. Adding a drive task to drive the transaction packet to the interface signal
//drive the transaction items to interface signals
task drive;
forever begin
transaction trans;
`DRIV_IF.wr_en <= 0;
`DRIV_IF.rd_en <= 0;
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
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
4. Adding a local variable to track the number of packets driven, and increment the variable in drive task
(This
will be useful to end the test-case/Simulation. i.e compare the
generated pkt’s and driven pkt’s, if both are same then end the
simulation)
//used to count the number of transactions
int no_transactions;
//drive the transaction items to interface signals
task drive;
------
------
no_transactions++;
endtask
5. Complete driver code.
class driver;
//used to count the number of transactions
int no_transactions;
//creating virtual interface handle
virtual mem_intf mem_vif;
//creating mailbox handle
mailbox gen2driv;
//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, Reset the Interface signals to default/initial values
task reset;
wait(mem_vif.reset);
$display("--------- [DRIVER] Reset Started ---------");
`DRIV_IF.wr_en <= 0;
`DRIV_IF.rd_en <= 0;
`DRIV_IF.addr <= 0;
`DRIV_IF.wdata <= 0;
wait(!mem_vif.reset);
$display("--------- [DRIVER] Reset Ended---------");
endtask
//drive the transaction items to interface signals
task drive;
forever begin
transaction trans;
`DRIV_IF.wr_en <= 0;
`DRIV_IF.rd_en <= 0;
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
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
endclass
Environment
Environment is container class contains Mailbox, Generator and Driver.
Creates the mailbox, generator and driver shares the mailbox handle across the Generator and Driver.
class environment; --- endclass
1. Declare the handles,
//generator and driver instance generator gen; driver driv; //mailbox handle's mailbox gen2driv; //event for synchronization between generator and test event gen_ended; //virtual interface virtual mem_intf mem_vif;
2. In Construct Method, 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();
//creating generator and driver
gen = new(gen2driv,gen_ended);
driv = new(mem_vif,gen2driv);
endfunction
3. For better accessibility.
Generator and Driver activity can be divided and controlled in three methods.
- pre_test() – Method to call Initialization. i.e, reset method.
- test() – Method to call Stimulus Generation and Stimulus Driving.
- post_test() – Method to wait the completion of generation and driving.
task pre_test();
driv.reset();
endtask
task test();
fork
gen.main();
driv.main();
join_any
endtask
task post_test();
wait(gen_ended.triggered);
wait(gen.repeat_count == driv.no_transactions);
endtask
4. Add a run task to call the above methods,
call $finish after post_test() to end the simulation.
task run;
pre_test();
test();
post_test();
$finish;
endtask
5. Complete environment class code.
`include "transaction.sv"
`include "generator.sv"
`include "driver.sv"
class environment;
//generator and driver instance
generator gen;
driver driv;
//mailbox handle's
mailbox gen2driv;
//event for synchronization between generator and test
event gen_ended;
//virtual interface
virtual mem_intf mem_vif;
//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();
//creating generator and driver
gen = new(gen2driv,gen_ended);
driv = new(mem_vif,gen2driv);
endfunction
task pre_test();
driv.reset();
endtask
task test();
fork
gen.main();
driv.main();
join_any
endtask
task post_test();
wait(gen_ended.triggered);
wait(gen.repeat_count == driv.no_transactions);
endtask
//run task
task run;
pre_test();
test();
post_test();
$finish;
endtask
endclass
Test
Test code is written with the program block.
The test is responsible for,
- Creating the environment.
- Configuring the testbench i.e, setting the type and number of transactions to be generated.
- Initiating the stimulus driving.
program test; ---- endprogram
1. Declare and Create an environment,
//declaring environment instance
environment env;
initial begin
//creating environment
env = new(intf);
end
2. Configure the number of transactions to be generated,
//setting the repeat count of generator as 10, means to generate 10 packets
env.gen.repeat_count = 10;
3. Initiating the stimulus driving,
//calling run of env, it interns calls generator and driver main tasks.
env.run();
4. Complete Test Code,
`include "environment.sv"
program test(mem_intf intf);
//declaring environment instance
environment env;
initial begin
//creating environment
env = new(intf);
//setting the repeat count of generator as 10, means to generate 10 packets
env.gen.repeat_count = 10;
//calling run of env, it interns calls generator and driver main tasks.
env.run();
end
endprogram
TestBench Top
- This is the topmost file, which connects the DUT and TestBench.
- TestBench top consists of DUT, Test and Interface instances.
- The interface connects the DUT and TestBench.
module tbench_top; --- endmodule
1. Declare and Generate the clock and reset,
//clock and reset signal declaration
bit clk;
bit reset;
//clock generation
always #5 clk = ~clk;
//reset Generation
initial begin
reset = 1;
#5 reset =0;
end
2. Create Interface instance,
//creatinng instance of interface, inorder to connect DUT and testcase mem_intf intf(clk,reset);
3. Create Design Instance and Connect Interface signals,
//DUT instance, interface signals are connected to the DUT ports
memory DUT (
.clk(intf.clk),
.reset(intf.reset),
.addr(intf.addr),
.wr_en(intf.wr_en),
.rd_en(intf.rd_en),
.wdata(intf.wdata),
.rdata(intf.rdata)
);
4. Create a test instance and Pass the interface handle,
//Testcase instance, interface handle is passed to test as an argument test t1(intf);
5. Add logic to generate the dump,
initial begin
$dumpfile("dump.vcd"); $dumpvars;
end
6. Complete testbench top code,
`include "interface.sv"
`include "random_test.sv"
module tbench_top;
//clock and reset signal declaration
bit clk;
bit reset;
//clock generation
always #5 clk = ~clk;
//reset Generation
initial begin
reset = 1;
#5 reset =0;
end
//creatinng instance of interface, inorder to connect DUT and testcase
mem_intf intf(clk,reset);
//Testcase instance, interface handle is passed to test as an argument
test t1(intf);
//DUT instance, interface signals are connected to the DUT ports
memory DUT (
.clk(intf.clk),
.reset(intf.reset),
.addr(intf.addr),
.wr_en(intf.wr_en),
.rd_en(intf.rd_en),
.wdata(intf.wdata),
.rdata(intf.rdata)
);
//enabling the wave dump
initial begin
$dumpfile("dump.vcd"); $dumpvars;
end
endmodule
Edit and Execute the Memory Model TestBench code in EDA Playground.
