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.