UVM TestBench Example


-: Internal Links :-          
 Sequence_item             
 Sequence                      
 Sequencer                     
 Driver                            
 Monitor                         
 Agent                            
 Environment           
 Test                               
 Scoreboard                    
 TestBench Top             
 EDAPlayground Link     

     UVM TestBench to verify Memory Model 


    For Design specification and Verification plan, refer to Memory Model.

     UVM TestBecnh architecture 


    *click on image for better view

    To maintain uniformity in naming the components/objects, all the component/object name's are starts with mem_*.

     TestBench Components/Objects 

     Sequence item                                                                                                                            

    • fields required to generate the stimulus are declared in the sequence_item.
    • sequence_item can be used as placeholder for the activity monitored by monitor on DUT signals.

    1. sequence_item is written by extending uvm_seq_item;

    class mem_seq_item extends uvm_sequence_item;  
    
      //Utility macro
      `uvm_object_utils(mem_seq_item)  
    
      //Constructor
      function new(string name = "mem_seq_item");
        super.new(name);
      endfunction
    
    endclass

    2. Declaring the fields in mem_seq_item,

    class mem_seq_item extends uvm_sequence_item; 
    
      //data and control fields
      bit [3:0] addr;
      bit       wr_en;
      bit       rd_en;
      bit [7:0] wdata;
      bit [7:0] rdata;
    
      //Utility macro
      `uvm_object_utils(mem_seq_item) 
    
      //Constructor
      function new(string name = "mem_seq_item");
        super.new(name);
      endfunction
    endclass

    3. To generate the random stimulus, declare the fields as rand.

    class mem_seq_item extends uvm_sequence_item; 
    
      //data and control fields
      rand bit [3:0] addr;
      rand bit       wr_en;
      rand bit       rd_en;
      rand bit [7:0] wdata;
           bit [7:0] rdata;
    
      //Utility macro
      `uvm_object_utils(mem_seq_item) 
    
      //Constructor
      function new(string name = "mem_seq_item");
        super.new(name);
      endfunction
    endclass

    4. In order to use the uvm_object methods ( copy, compare, pack, unpack, record, print, and etc ),
    all the fields are registered to uvm_field_* macros.

    class mem_seq_item extends uvm_sequence_item;
      //data and control fields
      rand bit [3:0] addr;
      rand bit       wr_en;
      rand bit       rd_en;
      rand bit [7:0] wdata;
           bit [7:0] rdata;
      
      //Utility and Field macros,
      `uvm_object_utils_begin(mem_seq_item)
        `uvm_field_int(addr,UVM_ALL_ON)
        `uvm_field_int(wr_en,UVM_ALL_ON)
        `uvm_field_int(rd_en,UVM_ALL_ON)
        `uvm_field_int(wdata,UVM_ALL_ON)
      `uvm_object_utils_end
      
      //Constructor
      function new(string name = "mem_seq_item");
        super.new(name);
      endfunction
      
    endclass
    

    5. Either write or read operation will be performed at once, so constraint is added to generate wr_en and rd_en.

    class mem_seq_item extends uvm_sequence_item;
      //data and control fields
      rand bit [3:0] addr;
      rand bit       wr_en;
      rand bit       rd_en;
      rand bit [7:0] wdata;
           bit [7:0] rdata;
      
      //Utility and Field macros,
      `uvm_object_utils_begin(mem_seq_item)
        `uvm_field_int(addr,UVM_ALL_ON)
        `uvm_field_int(wr_en,UVM_ALL_ON)
        `uvm_field_int(rd_en,UVM_ALL_ON)
        `uvm_field_int(wdata,UVM_ALL_ON)
      `uvm_object_utils_end
      
      //Constructor
      function new(string name = "mem_seq_item");
        super.new(name);
      endfunction
      
      //constaint, to generate any one among write and read
      constraint wr_rd_c { wr_en != rd_en; }; 
      
    endclass

     Complete mem_seq_item code, 

    class mem_seq_item extends uvm_sequence_item;
      //data and control fields
      rand bit [3:0] addr;
      rand bit       wr_en;
      rand bit       rd_en;
      rand bit [7:0] wdata;
           bit [7:0] rdata;
      
      //Utility and Field macros,
      `uvm_object_utils_begin(mem_seq_item)
        `uvm_field_int(addr,UVM_ALL_ON)
        `uvm_field_int(wr_en,UVM_ALL_ON)
        `uvm_field_int(rd_en,UVM_ALL_ON)
        `uvm_field_int(wdata,UVM_ALL_ON)
      `uvm_object_utils_end
      
      //Constructor
      function new(string name = "mem_seq_item");
        super.new(name);
      endfunction
      
      //constaint, to generate any one among write and read
      constraint wr_rd_c { wr_en != rd_en; }; 
      
    endclass
    

     Sequence                                                                                                                                   

    • Sequence generates the stimulus and sends to driver via sequencer.
    • An agent can have any number of sequences.

    1. Sequecne is written by extending the uvm_sequence.

    class mem_sequence extends uvm_sequence#(mem_seq_item);
      
      `uvm_sequence_utils(mem_sequence,mem_sequencer)
      
      //Constructor
      function new(string name = "mem_sequence");
        super.new(name);
      endfunction
      
    endclass
    


    2. Logic to generate and sending the sequence_item is added inside the body() method.

    class mem_sequence extends uvm_sequence#(mem_seq_item);
      
      `uvm_sequence_utils(mem_sequence,mem_sequencer)
      
      //Constructor
      function new(string name = "mem_sequence");
        super.new(name);
      endfunction
      
      virtual task body();
    
        req = mem_seq_item::type_id::create("req");
        wait_for_grant();
        req.randomize();
        send_request(req);
        wait_for_item_done();
    
      endtask
      
    endclass
    


    write sequence,

    class mem_wr_seq extends uvm_sequence#(mem_seq_item);
      
      `uvm_object_utils(mem_wr_seq)
       
      //Constructor
      function new(string name = "mem_wr_seq");
        super.new(name);
      endfunction
      
      virtual task body();
        `uvm_do_with(req,{req.wr_en == 1;})
      endtask
      
    endclass
    
    
    read sequence,

    class mem_rd_seq extends uvm_sequence#(mem_seq_item);
      
      `uvm_object_utils(mem_rd_seq)
       
      //Constructor
      function new(string name = "mem_rd_seq");
        super.new(name);
      endfunction
      
      virtual task body();
        `uvm_do_with(req,{req.rd_en == 1;})
      endtask
      
    endclass
    

     Sequencer                                                                                                                                  

    Sequencer is written by extending uvm_sequencer, there is no extra logic required to be added in sequencer.

    class mem_sequencer extends uvm_sequencer#(mem_seq_item);
    
      `uvm_component_utils(mem_sequencer) 
    
      //constructor
      function new(string name, uvm_component parent);
        super.new(name,parent);
      endfunction
      
    endclass
    

     Driver                                                                                                                                        

    driver receives the stimulus from sequence via sequencer and drives on interface signals.

    1. driver is written by extending the uvm_driver

    class mem_driver extends uvm_driver #(mem_seq_item);
    
      `uvm_component_utils(mem_driver)
    
      // Constructor
      function new (string name, uvm_component parent);
        super.new(name, parent);
      endfunction : new
    
    endclass : mem_driver
    

    2. Declare the virtual interface, 

      // Virtual Interface
      virtual mem_if vif;

    3. Get the interface handle using get config_db, 

    if(!uvm_config_db#(virtual mem_if)::get(this, "", "vif", vif))
           `uvm_fatal("NO_VIF",{"virtual interface must be set for: ",get_full_name(),".vif"});
    

    4. adding the get config_db in the build_phase, 

      function void build_phase(uvm_phase phase);
        super.build_phase(phase);
         if(!uvm_config_db#(virtual mem_if)::get(this, "", "vif", vif))
           `uvm_fatal("NO_VIF",{"virtual interface must be set for: ",get_full_name(),".vif"});
      endfunction: build_phase
    

    5. Add driving logic. get the seq_item and drive to DUT signals, 

      // run phase
      virtual task run_phase(uvm_phase phase);
        forever begin
        seq_item_port.get_next_item(req);
         ......
         .. driving logic ..
         ......
        seq_item_port.item_done();
        end
      endtask : run_phase
    

     Complete driver code, 

    class mem_driver extends uvm_driver #(mem_seq_item);
    
      // Virtual Interface
      virtual mem_if vif;
    
      `uvm_component_utils(mem_driver)
        
      //uvm_analysis_port #(mem_seq_item) Drvr2Sb_port;
    
      // Constructor
      function new (string name, uvm_component parent);
        super.new(name, parent);
      endfunction : new
    
      function void build_phase(uvm_phase phase);
        super.build_phase(phase);
         if(!uvm_config_db#(virtual mem_if)::get(this, "", "vif", vif))
           `uvm_fatal("NO_VIF",{"virtual interface must be set for: ",get_full_name(),".vif"});
      endfunction: build_phase
    
      // run phase
      virtual task run_phase(uvm_phase phase);
        forever begin
        seq_item_port.get_next_item(req);
        //respond_to_transfer(req);
        drive();
        seq_item_port.item_done();
        end
      endtask : run_phase
    
      // drive 
      virtual task drive();
        req.print();
          `DRIV_IF.wr_en <= 0;
          `DRIV_IF.rd_en <= 0;
          @(posedge vif.DRIVER.clk);
          `DRIV_IF.addr <= req.addr;
        if(req.wr_en) begin
            `DRIV_IF.wr_en <= req.wr_en;
            `DRIV_IF.wdata <= req.wdata;
          //$display("\tADDR = %0h \tWDATA = %0h",req.addr,trans.wdata);
            @(posedge vif.DRIVER.clk);
          end
        if(req.rd_en) begin
            `DRIV_IF.rd_en <= req.rd_en;
            @(posedge vif.DRIVER.clk);
            `DRIV_IF.rd_en <= 0;
            @(posedge vif.DRIVER.clk);
            req.rdata = `DRIV_IF.rdata;
           // $display("\tADDR = %0h \tRDATA = %0h",trans.addr,`DRIV_IF.rdata);
          end
          $display("-----------------------------------------");
      endtask : drive
    
    endclass : mem_driver
    

     Monitor                                                                                                                                     
    • Monitor samples the DUT signals through virtual interface and converts the signal level activity to transaction level.
    1. Monitor is written by extending the uvm_monitor.

    class mem_monitor extends uvm_monitor;
    
      `uvm_component_utils(mem_monitor)
    
      // new - constructor
      function new (string name, uvm_component parent);
        super.new(name, parent);
      endfunction : new
    
    endclass : mem_monitor
    

    2. Declare virtual interface, 

      // Virtual Interface
      virtual mem_if vif;
    

    3. Connect interface to Virtual interface by using get method, 

      function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        if(!uvm_config_db#(virtual mem_if)::get(this, "", "vif", vif))
           `uvm_fatal("NOVIF",{"virtual interface must be set for: ",get_full_name(),".vif"});
      endfunction: build_phase
    

    4. Declare analysis port, 

      uvm_analysis_port #(mem_seq_item) item_collected_port;
    

    5. Declare seq_item handle, Used as place holder for sampled signal activity, 

      mem_seq_item trans_collected;
    

    6. Add Sampling logic in run_phase, 

    • sample the interface signal and assign to trans_collected handle
    • sampling logic is placed in forever loop

      // run phase
      virtual task run_phase(uvm_phase phase);
        forever begin
          //sampling logic
          
            @(posedge vif.MONITOR.clk);
            wait(vif.monitor_cb.wr_en || vif.monitor_cb.rd_en);
            trans_collected.addr = vif.monitor_cb.addr;
          if(vif.monitor_cb.wr_en) begin
            trans_collected.wr_en = vif.monitor_cb.wr_en;
            trans_collected.wdata = vif.monitor_cb.wdata;
            trans_collected.rd_en = 0;
            @(posedge vif.MONITOR.clk);
          end
          if(vif.monitor_cb.rd_en) begin
            trans_collected.rd_en = vif.monitor_cb.rd_en;
            trans_collected.wr_en = 0;
            @(posedge vif.MONITOR.clk);
            @(posedge vif.MONITOR.clk);
            trans_collected.rdata = vif.monitor_cb.rdata;
    end 
        end  
      endtask : run_phase

    7. After sampling, by using write method send the sampled transaction packet to the scoreboard, 

      item_collected_port.write(trans_collected);
    

     Complete monitor code, 

    class mem_monitor extends uvm_monitor;
    
      // Virtual Interface
      virtual mem_if vif;
    
      uvm_analysis_port #(mem_seq_item) item_collected_port;
    
      // Placeholder to capture transaction information.
      mem_seq_item trans_collected;
    
      `uvm_component_utils(mem_monitor)
    
      // new - constructor
      function new (string name, uvm_component parent);
        super.new(name, parent);
        trans_collected = new();
        item_collected_port = new("item_collected_port", this);
      endfunction : new
    
      function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        if(!uvm_config_db#(virtual mem_if)::get(this, "", "vif", vif))
           `uvm_fatal("NOVIF",{"virtual interface must be set for: ",get_full_name(),".vif"});
      endfunction: build_phase
    
      // run phase
      virtual task run_phase(uvm_phase phase);
        forever begin
          @(posedge vif.MONITOR.clk);
          wait(vif.monitor_cb.wr_en || vif.monitor_cb.rd_en);
            trans_collected.addr = vif.monitor_cb.addr;
          if(vif.monitor_cb.wr_en) begin
            trans_collected.wr_en = vif.monitor_cb.wr_en;
            trans_collected.wdata = vif.monitor_cb.wdata;
            trans_collected.rd_en = 0;
            @(posedge vif.MONITOR.clk);
          end
          if(vif.monitor_cb.rd_en) begin
            trans_collected.rd_en = vif.monitor_cb.rd_en;
            trans_collected.wr_en = 0;
            @(posedge vif.MONITOR.clk);
            @(posedge vif.MONITOR.clk);
            trans_collected.rdata = vif.monitor_cb.rdata;
          end
          item_collected_port.write(trans_collected);
        end 
      endtask : run_phase
    
    endclass : mem_monitor

     Agent                                                                                                                                         

    An agent is a container class contains a driver,sequencer, and monitor.

    1. agent is written by extending the uvm_agent,


    class mem_agent extends uvm_agent;
    
      // UVM automation macros for general components
      `uvm_component_utils(mem_agent)
    
      // constructor
      function new (string name, uvm_component parent);
        super.new(name, parent);
      endfunction : new
    
    endclass : mem_agent

    2. Declare driver, sequencer and monitor instance, 

      //declaring agent components
      mem_driver    driver;
      mem_sequencer sequencer;
      mem_monitor   monitor;
    


    3. Depending on Agent type, create agent components in build phase, 
    driver and sequencer will be crated only for active agent.

      // build_phase
      function void build_phase(uvm_phase phase);
        super.build_phase(phase);
    
        if(get_is_active() == UVM_ACTIVE) begin
          driver    = mem_driver::type_id::create("driver", this);
          sequencer = mem_sequencer::type_id::create("sequencer", this);
        end
    
        monitor = mem_monitor::type_id::create("monitor", this);
      endfunction : build_phase

    4. Connect the driver seq_item_port to sequencer seq_item_export for communication between driver and sequencer in connect phase. 

      // connect_phase
      function void connect_phase(uvm_phase phase);
        if(get_is_active() == UVM_ACTIVE) begin
          driver.seq_item_port.connect(sequencer.seq_item_export);
        end
      endfunction : connect_phase

     Complete Agent code, 

    class mem_agent extends uvm_agent;
      //declaring agent components
      mem_driver    driver;
      mem_sequencer sequencer;
      mem_monitor   monitor;
    
      // UVM automation macros for general components
      `uvm_component_utils(mem_agent)
    
      // constructor
      function new (string name, uvm_component parent);
        super.new(name, parent);
      endfunction : new
    
      // build_phase
      function void build_phase(uvm_phase phase);
        super.build_phase(phase);
    
        if(get_is_active() == UVM_ACTIVE) begin
          driver = mem_driver::type_id::create("driver", this);
          sequencer = mem_sequencer::type_id::create("sequencer", this);
        end
    
        monitor = mem_monitor::type_id::create("monitor", this);
      endfunction : build_phase
    
      // connect_phase
      function void connect_phase(uvm_phase phase);
        if(get_is_active() == UVM_ACTIVE) begin
          driver.seq_item_port.connect(sequencer.seq_item_export);
        end
      endfunction : connect_phase
    
    endclass : mem_agent

     Scoreboard                                                                                                                                

    scoreboard receives the transaction from monitor and compares with the reference values.

    1. Scoreboard is written by extending uvm_scoreboard,

    class mem_scoreboard extends uvm_scoreboard;
    
      `uvm_component_utils(mem_scoreboard)
    
      // new - constructor
      function new (string name, uvm_component parent);
        super.new(name, parent);
      endfunction : new
    
    endclass : mem_scoreboard

    2. Declare and Create TLM Analysis port, ( to receive transaction pkt from Monitor).

    //Declaring port
    uvm_analysis_imp#(mem_seq_item, mem_scoreboard) item_collected_export;
    
    //creating port
    item_collected_export = new("item_collected_export", this);
    

    3. analysis export of Scoreboard is connected to Monitor port. (Connection is done in environment connect phase)

    monitor.item_collected_port.connect(scoreboard.item_collected_export);
    

    4. write method of scoreboard will receive the transaction packet from monitor, on calling write method from monitor.


     
      //calling write method from monitor
      item_collected_port.write(pkt);
     
      //scoreboard write function
      virtual function void write(mem_seq_item pkt);
        pkt.print();
      endfunction : write
    

    6. Add Sampling logic in run_phase, 

      // run phase
      virtual task run_phase(uvm_phase phase);
        --- comparision logic ---    
      endtask : run_phase

     Complete scoreboard code 

    class mem_scoreboard extends uvm_scoreboard;
    
      `uvm_component_utils(mem_scoreboard)
      uvm_analysis_imp#(mem_seq_item, mem_scoreboard) item_collected_export;
    
      // new - constructor
      function new (string name, uvm_component parent);
        super.new(name, parent);
      endfunction : new
    
      function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        item_collected_export = new("item_collected_export", this);
      endfunction: build_phase
      
      // write
      virtual function void write(mem_seq_item pkt);
        $display("SCB:: Pkt recived");
        pkt.print();
      endfunction : write
    
      // run phase
      virtual task run_phase(uvm_phase phase);
        --- comparision logic ---    
      endtask : run_phase
    endclass : mem_scoreboard
    

     Environment/env                                                                                                                       

    Environment is the container class, It contains one or more agents, as well as other components such as scoreboard, top level monitor, and checker.

    1. Environment is written by extending the uvm_env,

    class mem_model_env extends uvm_env;
      
      `uvm_component_utils(mem_model_env)
        
      // new - constructor
      function new(string name, uvm_component parent);
        super.new(name, parent);
      endfunction : new
    
    endclass : mem_model_env
    

    2. Declare the agent and scoreboard, 

      mem_agent      mem_agnt;
    
      mem_scoreboard mem_scb;

    3. Create agent and scoreboard, 

      mem_agnt = mem_agent::type_id::create("mem_agnt", this);
    
      mem_scb  = mem_scoreboard::type_id::create("mem_scb", this);

    3. Connecting monitor port to scoreboard port, 

      mem_agnt.monitor.item_collected_port.connect(mem_scb.item_collected_export);

     Complete environment code, 

    class mem_model_env extends uvm_env;
      
      //---------------------------------------
      // agent and scoreboard instance
      //---------------------------------------
      mem_agent      mem_agnt;
      mem_scoreboard mem_scb;
      
      `uvm_component_utils(mem_model_env)
      
      //--------------------------------------- 
      // constructor
      //---------------------------------------
      function new(string name, uvm_component parent);
        super.new(name, parent);
      endfunction : new
    
      //---------------------------------------
      // build_phase - crate the components
      //---------------------------------------
      function void build_phase(uvm_phase phase);
        super.build_phase(phase);
    
        mem_agnt = mem_agent::type_id::create("mem_agnt", this);
        mem_scb  = mem_scoreboard::type_id::create("mem_scb", this);
      endfunction : build_phase
      
      //---------------------------------------
      // connect_phase - connecting monitor and scoreboard port
      //---------------------------------------
      function void connect_phase(uvm_phase phase);
        mem_agnt.monitor.item_collected_port.connect(mem_scb.item_collected_export);
      endfunction : connect_phase
    
    endclass : mem_model_env
    

     Test                                                                                                                                           

    Test defines the test scenario for the testbench.

    1. test is written by extending the uvm_test,


    class mem_model_test extends uvm_test;
    
      `uvm_component_utils(mem_model_test)
    
      function new(string name = "mem_model_test",uvm_component parent=null);
        super.new(name,parent);
      endfunction : new
    
    endclass : mem_model_test
    

    2. Declare env and sequence, 

      mem_model_env env;
      mem_sequence  seq;
    

    3. Create env and sequence, 

      env = mem_model_env::type_id::create("env",this);
      seq = mem_sequence::type_id::create("seq");
    

    4. Start sequence, 

      seq.start(env.mem_agnt.sequencer);
    

     Complete Test code, 

    class mem_model_test extends uvm_test;
    
      `uvm_component_utils(mem_model_test)
    
      mem_model_env env;
      mem_sequence  seq;
    
      function new(string name = "mem_model_test",uvm_component parent=null);
        super.new(name,parent);
      endfunction : new
    
      virtual function void build_phase(uvm_phase phase);
        super.build_phase(phase);
    
        env = mem_model_env::type_id::create("env", this);
        seq = mem_sequence::type_id::create("seq");
      endfunction : build_phase
    
      task run_phase(uvm_phase phase);
        phase.raise_objection(this);
          seq.start(env.mem_agnt.sequencer);
        phase.drop_objection(this);
      endtask : run_phase
    
    endclass : mem_model_test

     TestBench_Top                                                                                                                          

    TestBench top is the module, it connects the DUT and Verification environment components.

    Testbench_top contains,
    • DUT instance
    • interface instance 
    • run_test() method 
    • virtual interface set config_db
    • clock and reset generation logic
    • wave dump logic

    *click on image for better view

    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_if intf(clk,reset);
      
      //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 
        uvm_config_db#(virtual mem_if)::set(uvm_root::get(),"*","vif",intf);
        $dumpfile("dump.vcd"); $dumpvars;
      end
      
      initial begin 
        run_test();
      end
    endmodule
    



    Edit and Execute the Memory Model UVM TestBench code in EDA Playground.


    Execute the above code on 

    Click on EDA playground image.

    Leave your Comments here,


    "Your valuable inputs are required to improve the quality"