SystemVerilog TestBench Example – Memory

Memory Model TestBench With Monitor and Scoreboard

TestBench Architecture:

SystemVerilog TestBench
SystemVerilog TestBench

Only monitor and scoreboard are explained here, Refer to ‘Memory Model’ 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 mem_intf mem_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 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

4. Complete monitor code.

`define MON_IF mem_vif.MONITOR.monitor_cb
class monitor;
  
  //creating virtual interface handle
  virtual mem_intf mem_vif;
  
  //creating mailbox handle
  mailbox mon2scb;
  
  //constructor
  function new(virtual mem_intf mem_vif,mailbox mon2scb);
    //getting the interface
    this.mem_vif = mem_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 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
  
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;

  ------  
  
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 store wdata and compare rdata with stored data,

   //stores wdata and compare rdata with stored data
  task main;
    transaction trans;
    forever begin
      #50;
      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

3. Complete scoreboard code.

class scoreboard;
   
  //creating mailbox handle
  mailbox mon2scb;
  
  //used to count the number of transactions
  int no_transactions;
  
  //array to use as local memory
  bit [7:0] mem[4];
  
  //constructor
  function new(mailbox mon2scb);
    //getting the mailbox handles from  environment 
    this.mon2scb = mon2scb;
    foreach(mem[i]) mem[i] = 8'hFF;
  endfunction
  
  //stores wdata and compare rdata with stored data
  task main;
    transaction trans;
    forever begin
      #50;
      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
  
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 mem_intf mem_vif;

2. In Construct Method, Create

  • Mailbox (mon2scb)
  • Monitor
  • Scoreboard

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();
    mon2scb  = new();
    
    //creating generator and driver
    gen  = new(gen2driv,gen_ended);
    driv = new(mem_vif,gen2driv);
    mon  = new(mem_vif,mon2scb);
    scb  = new(mon2scb);
  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,

class environment;
  
  //generator and driver instance
  generator  gen;
  driver     driv;
  monitor    mon;
  scoreboard scb;
  
  //mailbox handle's
  mailbox gen2driv;
  mailbox mon2scb;
  
  //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();
    mon2scb  = new();
    
    //creating generator and driver
    gen  = new(gen2driv,gen_ended);
    driv = new(mem_vif,gen2driv);
    mon  = new(mem_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);
    wait(gen.repeat_count == scb.no_transactions);
  endtask  
  
  //run task
  task run;
    pre_test();
    test();
    post_test();
    $finish;
  endtask
  
endclass

Edit and Execute Memory Model TestBench code in EDA Playground.

Click to execute on   

❮ Previous