Friday, July 21, 2023

UVM verification environment universal template 2.0

 System Verilog RTL verification

UVM verification environment universal template 2.0

UVM verification environment starting point for use in verification of any RTL Digital Design Under Test (DUT)


Introduction 2

UVM verification environment  architecture 2

DUT 3

Interface 4

Sequence item class templates evolution 4

Sequence item class  template 1.0 ( an OBJECT class) 4

Sequence item ( or transaction ) class  template 2.0 ( an OBJECT class,  file: sequence_item.sv ) 5

Sequence class template evolution 7

Base sequence class template 1.0 ( an OBJECT class, file: sequence.sv ) 7

Sequence class template 2.0 ( an OBJECT class, file: sequence.sv ) 9

Sequencer class  template evolution 12

Sequencer class  template 1.0 ( once more Component class ) 12

Sequencer class  template 2.0 ( once more a Component class: sequencer.sv ) 13

Driver class  template evolution 16

Driver class  template 1.0 ( one more Component class ) 16

Driver class  template 2.0 ( one more Component class: file driver.sv ) 18

Monitor class  template evolution 22

Monitor class  template 1.0 ( one more Component class ) 22

Monitor class  template 2.0 ( one more Component class: file monitor.sv ) 23

Scoreboard template ( A Component class file : scoreboard.sv ) 28

Agent class template evolution 32

Agent class template 1.0 ( once more Component class ) 32

Agent class template 2.0 ( once more a Component class: file agent.sv ) 33

Environment class template evolution 36

Environment class template 1.0 36

Environment class template 2.0 ( A Component class: file env.sv) 38

UVM Test Component template evolution 42

UVM Test Component template 1.0 42

UVM Test Component template 2.0 44

testbench.sv (top testbench module ) 49

Compilation 53

Introduction

In this blog post there is a source code of all UVM verification environment files to use, as a starting point,  in verification of any RTL Digital Design Under Test (DUT). The assembly of files of  the UVM verification template compiles OK.

To achieve the successful compilation of the UVM verification environment universal template files, in this case,  a Scorebord.sv file is not present ( empty file) and a dummy DUT ( SystemVerilog RTL module and its corresponding SystemVerilog interface)   is included.

UVM verification environment  architecture


For UVM verification  of any DUT what  we need to think about is an architecture so our architecture will contain files organized in a  System Verilog UVM hierarchy as described in previously showed  block diagram 

  • top testbench module :  testbench.sv

    • Design file 

      • DUT.sv 

    • UVM verification infrastructure files

      •  Interface.sv ( SystemVerilog interface of a desired DUT ) 

      • UVM verification classes ( one class per file and  per  UVM block in previously presented block diagram) :

        • test class (COMPONENT class, test.sv)

          • environment class (COMPONENT class, env.sv) 

          • scoreboard  class (COMPONENT class, scoreboard.sv)

          • agent class ( COMPONENT class, agent.sv)  

            • driver (COMPONENT class, driver.sv) 

            • monitor (COMPONENT class, monitor.sv)  

            • sequencer (COMPONENT class, sequencer.sv) 

        • sequence/sequence item ( wo OBJECT classes in one file:  sequence.sv)

DUT 

module SimpleDUT (
    input clk,
    input reset,
    input [3:0] din_i,
    output [3:0] dout_o
);
    // Logic for the DUT
    assign dout_o = din_i;
endmodule


Interface

interface SimpleDUT_interface(input logic clock );
  logic reset;
  logic [3:0] din_i, dout_o;
endinterface: SimpleDUT_interface


Sequence item class templates evolution

Sequence item class  template 1.0 ( an OBJECT class)

The sequence item class  is an object class and not a component class so I won't copy

everything from the previous component class  templates.


//Object class
class SimpleDUT_sequence_item extends uvm_sequence_item;
// just like the component class this class will also need a macro but it will be UVM object utils
  `uvm_object_utils(SimpleDUT_sequence_item)

// Instantiation logic/wires/registers same as used in interface go here
// so in other words: straight copy from interface
// We are going to use this to generate random values about DUT input port values on one hand and to store the output result on the other hand.
// So to do that what we are going to do is declare these signals as DUT input port values Rand signals


  //--------------------------------------------------------
  //Instantiation
  //--------------------------------------------------------
  rand logic [3:0] din_i;
  logic [3:0] dout_o;

// let's see how Constructor for an object class will be defined: 

// In object class Constructor  we just pass the name which can be done  like this
  //--------------------------------------------------------
  //Constructor
  //--------------------------------------------------------
  function new(string name = "SimpleDUT_sequence_item");
    super.new(name);

  endfunction: new

endclass: SimpleDUT_sequence_item


Sequence item ( or transaction ) class  template 2.0 ( an OBJECT class,  file: sequence_item.sv )

The first step in verifying a RTL design is defining what kind of data should be sent to the DUT.

The concept of transaction means the smallest data transfers that can be executed in a verification model. For our dummy DUT there is only one data transfer possible for applied input data din_i, output data dout_o = din_i. In general we could expect that DUT also has Power On Reset signal so we will keep transfer of RST_N in the transaction too, although RST_N is not connected in our dummy DUT.    

//Object class
class SimpleDUT_sequence_item extends uvm_sequence_item;
// just like the component class this class will also need a macro but it will be UVM object utils
  `uvm_object_utils(SimpleDUT_sequence_item)

// Instantiation logic/wires/registers same as used in interface go here
// so in other words: straight copy from interface
// We are going to use this to generate random values about DUT input port values on one hand and to store the output result on the other hand.
// So to do that what we are going to do is declare these signals as DUT input port values Rand signals

  //--------------------------------------------------------
  //Instantiation
  //--------------------------------------------------------
  rand logic reset;
  rand logic [3:0] din_i;
  logic [3:0] dout_o;

// 1. change of SimpleDUT_sequence_item template 1.0:  let's put some default constraints that we want in our sequence item

  //--------------------------------------------------------
  //Default Constraints
  //--------------------------------------------------------
  constraint input1_c { din_i inside {[0:15]};}

// let's see how Constructor for an object class will be defined:
// In object class Constructor  we just pass the name which can be done  like this
  //--------------------------------------------------------
  //Constructor
  //--------------------------------------------------------
  function new(string name = "SimpleDUT_sequence_item");
    super.new(name);

  endfunction: new

endclass: SimpleDUT_sequence_item


Sequence class template evolution

Base sequence class template 1.0 ( an OBJECT class, file: sequence.sv )

class SimpleDUT_base_sequence extends uvm_sequence;
  `uvm_object_utils(SimpleDUT_base_sequence)

  //--------------------------------------------------------
  //Constructor
  //--------------------------------------------------------
  function new(string name= "SimpleDUT_base_sequence");
    super.new(name);
    `uvm_info("BASE_SEQ", "Inside Constructor!", UVM_HIGH)
  endfunction

// in sequence class we need to define
// body describing what is going to happen  when we call this sequence

  //--------------------------------------------------------
  //Body Task
  //--------------------------------------------------------
  task body();
    `uvm_info("BASE_SEQ", "Inside body task!", UVM_HIGH)

     
  endtask: body
 
endclass: SimpleDUT_base_sequence



class SimpleDUT_test_sequence extends SimpleDUT_base_sequence;
  `uvm_object_utils(SimpleDUT_test_sequence)

  SimpleDUT_sequence_item item;

  //--------------------------------------------------------
  //Constructor
  //--------------------------------------------------------
  function new(string name= "SimpleDUT_test_sequence");
    super.new(name);
    `uvm_info("TEST_SEQ", "Inside Constructor!", UVM_HIGH)
  endfunction

 
  //--------------------------------------------------------
  //Body Task
  //--------------------------------------------------------
  task body();
    `uvm_info("TEST_SEQ", "Inside body task!", UVM_HIGH)
 
  endtask: body

 
endclass: SimpleDUT_test_sequence


Sequence class template 2.0 ( an OBJECT class, file: sequence.sv )

Now that we have a transaction, the next step is to create a sequence.

After a basic transaction has been specified, the verification environment will need to generate a collection of them and get them ready to be sent to the driver. This is a job for the sequence. Sequences are an ordered collection of transactions, they shape transactions to our needs and generate as many as we want. 

class SimpleDUT_base_sequence extends uvm_sequence;
  `uvm_object_utils(SimpleDUT_base_sequence)
//1. Change based on Base sequence item class template 1.0:  
// In a sequence class  we need to start a basic sequence.
// This means that the sequence class  will start or generate a sequence item class packets and then send them to the driver.
// In order to do that we need to instantiate a sequence item class over here.
// let's call it reset_pk
SimpleDUT_sequence_item reset_pkt;



  //--------------------------------------------------------
  //Constructor
  //--------------------------------------------------------
  function new(string name= "SimpleDUT_base_sequence");
    super.new(name);
    `uvm_info("BASE_SEQ", "Inside Constructor!", UVM_HIGH)
  endfunction

// in sequence class we need to define
// body describing what is going to happen  when we call this sequence

  //--------------------------------------------------------
  //Body Task
  //--------------------------------------------------------
  task body();
    `uvm_info("BASE_SEQ", "Inside body task!", UVM_HIGH)
    // 2. Change based on Base sequence item class template 1.0:
// Further we need to send the reset_pkt to the driver
// First step is to create the packet. for this so we say reset_pkt = SimpleDUT_sequence_item ....,
// and then  we call the "create" method of type_id create.
// Finally we say that a name of our packet is "reset_pkt"( you can give any name here)
reset_pkt = SimpleDUT_sequence_item::type_id::create("reset_pkt");

// 3. Change based on Base sequence item class template 1.0:
// Next we need to start_item and finish_item.
// Here we want to randomize that package although with "reset" constraint to be only one value: 1.
// As a result this sequence will just reset the DUT ( assumption here is that "reset" is active high.
start_item(reset_pkt);
  // Since constraint on the "reset" tigh reset to 1
  // this sequence will just reset the duty that we have ( the reset is active high )

      reset_pkt.randomize() with {reset==1;};
    finish_item(reset_pkt);

  endtask: body
 
endclass: SimpleDUT_base_sequence


// 4. Change based on Base sequence item class template 1.0:
//  we can also create another sequence which is not reset sequence
class SimpleDUT_test_sequence extends SimpleDUT_base_sequence;
  `uvm_object_utils(SimpleDUT_test_sequence)

  SimpleDUT_sequence_item item;

  //--------------------------------------------------------
  //Constructor
  //--------------------------------------------------------
  function new(string name= "SimpleDUT_test_sequence");
    super.new(name);
    `uvm_info("TEST_SEQ", "Inside Constructor!", UVM_HIGH)
  endfunction

 
  //--------------------------------------------------------
  //Body Task
  //--------------------------------------------------------
  task body();
    `uvm_info("TEST_SEQ", "Inside body task!", UVM_HIGH)

// 5. Change based on Base sequence item class template: 
// In this test sequence the randomized "reset" is equal to zero because now with this sequence we don't want uh to reset anything. This time  we want to drive some values to the input of DUT.
item = SimpleDUT_sequence_item::type_id::create("item");
    start_item(item);
      item.randomize() with {reset==0;};
    finish_item(item);

 
  endtask: body

 
endclass: SimpleDUT_test_sequence


Sequencer class  template evolution


Sequencer class  template 1.0 ( once more Component class )

Note #1: Same as driver class, the sequencer class  is a parameterized class so we need to pass the name of the sequence item class that we are going to use 

  • we haven't created the sequence item class but I know the name of it will be: SimpleDUT_sequence_item)

    • e.g. class SimpleDUT_sequencer extends uvm_sequencer#(SimpleDUT_sequence_item);

Note# 2: also in the sequencer we don't need the “run” phase so I'll just comment out  that part 

  • actually we are not going to use any phases in sequencer but just since this is a basic test bench let’s keep it here anyway.


// Note #1: Same as driver class, the sequencer class  is a parameterized class so we need to pass the name of the sequence item class that we are going to use
// we haven't created the sequence item class but I know the name of it will be SimpleDUT_sequence_item)

// class SimpleDUT_sequencer extends uvm_sequencer;
  class SimpleDUT_sequencer extends uvm_sequencer#(SimpleDUT_sequence_item);
  `uvm_component_utils(SimpleDUT_sequencer)

 
  //--------------------------------------------------------
  //Constructor
  //--------------------------------------------------------
  function new(string name = "SimpleDUT_sequencer", uvm_component parent);
    super.new(name, parent);
    `uvm_info("SEQUENCER_CLASS", "Inside Constructor!", UVM_HIGH)

  endfunction: new


  //--------------------------------------------------------
  //Build Phase
  //--------------------------------------------------------
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    `uvm_info("SEQUENCER_CLASS", "Build Phase!", UVM_HIGH)

endfunction: build_phase


  //--------------------------------------------------------
  //Connect Phase
  //--------------------------------------------------------
  function void connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    `uvm_info("SEQUENCER_CLASS", "Connect Phase!", UVM_HIGH)

  endfunction: connect_phase

  // Note# 2: also in the sequencer we don't need the Run phase so I'll just comment out  that part
  /*
  //--------------------------------------------------------
  //Run Phase
  //--------------------------------------------------------
  task run_phase (uvm_phase phase);
    super.run_phase(phase);
    `uvm_info("SEQUENCER_CLASS", "Run Phase!", UVM_HIGH)

  // Logic

    endtask: run_phase
  */

endclass: SimpleDUT_sequencer


Sequencer class  template 2.0 ( once more a Component class: sequencer.sv )


The sequencer is  responsible for sending the sequences to the driver.


The easiest way to include the sequencer in UVM testbench/infrastructure template is: 


typedef uvm_sequencer#(SimpleDUT_sequence_item) SimpleDUT_sequencer;

  

Note #1: Same as driver class, the sequencer class  is a parameterized class so we need to pass the name of the sequence item class that we are going to use 

  • we haven't created the sequence item class but I know the name of it will be: SimpleDUT_sequence_item)

    • e.g. class SimpleDUT_sequencer extends uvm_sequencer#(SimpleDUT_sequence_item);

Note# 2: also in the sequencer we don't need the “run” phase so I'll just comment out  that part 

  • actually we are not going to use any phases in sequencer but just since this is a basic test bench let’s keep it here anyway.


// Note #1: Same as driver class, the sequencer class  is a parameterized class so we need to pass the name of the sequence item class that we are going to use
// The name of sequence item class already created is SimpleDUT_sequence_item)

  class SimpleDUT_sequencer extends uvm_sequencer#(SimpleDUT_sequence_item);
  `uvm_component_utils(SimpleDUT_sequencer)

 
  //--------------------------------------------------------
  //Constructor
  //--------------------------------------------------------
  function new(string name = "SimpleDUT_sequencer", uvm_component parent);
    super.new(name, parent);
    `uvm_info("SEQUENCER_CLASS", "Inside Constructor!", UVM_HIGH)

  endfunction: new


  //--------------------------------------------------------
  //Build Phase
  //--------------------------------------------------------
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    `uvm_info("SEQUENCER_CLASS", "Build Phase!", UVM_HIGH)

endfunction: build_phase


  //--------------------------------------------------------
  //Connect Phase
  //--------------------------------------------------------
  function void connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    `uvm_info("SEQUENCER_CLASS", "Connect Phase!", UVM_HIGH)

  endfunction: connect_phase

  endclass:     SimpleDUT_sequencer

Driver class  template evolution


Driver class  template 1.0 ( one more Component class )

Note: driver class is a parameterized class so we need to pass the name of the sequence item class that we are going to use 

  • we haven't created the sequence item class but I know the name of it will be: SimpleDUT_sequence_item)

    • e.g. class SimpleDUT_driver extends uvm_driver#(SimpleDUT_sequence_item);

The driver knows what type of sequence item that we are going to drive on the sequencer and that is why we pass this as a parameter 


// Note: driver class is a parameterized class so we need to pass the name of the sequence item class that we are going to use
// we haven't created the sequence item class but I know the name of it will be SimpleDUT_sequence_item)

  class SimpleDUT_driver extends uvm_driver#(SimpleDUT_sequence_item);
  `uvm_component_utils(SimpleDUT_driver)

 
  //--------------------------------------------------------
  //Constructor
  //--------------------------------------------------------
  function new(string name = "SimpleDUT_driver", uvm_component parent);
    super.new(name, parent);
    `uvm_info("DRIVER_CLASS", "Inside Constructor!", UVM_HIGH)

  endfunction: new


  //--------------------------------------------------------
  //Build Phase
  //--------------------------------------------------------
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    `uvm_info("DRIVER_CLASS", "Build Phase!", UVM_HIGH)

endfunction: build_phase


  //--------------------------------------------------------
  //Connect Phase
  //--------------------------------------------------------
  function void connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    `uvm_info("DRIVER_CLASS", "Connect Phase!", UVM_HIGH)

  endfunction: connect_phase


  //--------------------------------------------------------
  //Run Phase
  //--------------------------------------------------------
  task run_phase (uvm_phase phase);
    super.run_phase(phase);
    `uvm_info("DRIVER_CLASS", "Run Phase!", UVM_HIGH)

  // Logic

    endtask: run_phase

endclass: SimpleDUT_driver


Driver class  template 2.0 ( one more Component class: file driver.sv )

The driver is a block whose role is to interact with the DUT. The driver pulls transactions from the sequencer and sends them repetitively to the signal-level interface.

The driver’s functionality should only be limited to send the necessary data to the DUT.


Note: driver class is a parameterized class so we need to pass the name of the sequence item ( in this case already defined: SimpleDUT_sequence_item   class) 

e.g. class SimpleDUT_driver extends uvm_driver#(SimpleDUT_sequence_item);

The driver knows what type of sequence item that we are going to drive on the sequencer and that is why we pass this as a parameter 


// Note: driver class is a parameterized class so we need to pass the name of the sequence item class that we are going to use ( in this case our previously defined sequence item class is SimpleDUT_sequence_item)
    class SimpleDUT_driver extends uvm_driver#(SimpleDUT_sequence_item);
  `uvm_component_utils(SimpleDUT_driver)

  // 1. driver class template ( 1.0) change:  We need to have access to the DUT interface ( here it has the name: "vif")
  // from the uvm_config_db.
// Also  we want to copy or use a name that we
// can be used throughout the driver for that class.
// We need to use "vif" so we do need a handle for this virtual interface
// so we do need to declare it in the class somewhere here.
// Finally what we declare is a virtual SimpleDUT_interface and the handle would be
// "vif". Now we know that this is virtual interface inside the driver
virtual SimpleDUT_interface vif;

//3. driver class template (1.0) change  :
// Here we are moving on  to update  of the Run phase of the driver
// which is the most important phase in the driver.
// But first:  the thing we need to do first is to  declare the handle to the item that we are going
// to use in the driver, and that is SimpleDUT_sequence_item
SimpleDUT_sequence_item item;



  //--------------------------------------------------------
  //Constructor
  //--------------------------------------------------------
  function new(string name = "SimpleDUT_driver", uvm_component parent);
    super.new(name, parent);
    `uvm_info("DRIVER_CLASS", "Inside Constructor!", UVM_HIGH)

  endfunction: new


  //--------------------------------------------------------
  //Build Phase
  //--------------------------------------------------------
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    `uvm_info("DRIVER_CLASS", "Build Phase!", UVM_HIGH)

//2. driver class template (1.0)  change  :In the top testbench  module we just set the interface now we need to get the interface so we can get that
// interface in  driver class and monitor class

// we can get that interface in any function that we want to but ideal way is to get it in the build phase
// The line: uvm_config_db #(virtual SimpleDUT_interface)::get(this, "*", "vif", vif) represents the usage of the uvm_config_db class and its get method in the context of the UVM (Universal Verification Methodology) framework in SystemVerilog.The get method is used to retrieve a configuration setting from the database.
if(!(uvm_config_db #(virtual SimpleDUT_interface)::get(this, "*", "vif", vif))) begin
      `uvm_error("DRIVER_CLASS", "Failed to get VIF from config DB!")
end

endfunction: build_phase


  //--------------------------------------------------------
  //Connect Phase
  //--------------------------------------------------------
  function void connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    `uvm_info("DRIVER_CLASS", "Connect Phase!", UVM_HIGH)

  endfunction: connect_phase


  //--------------------------------------------------------
  //Run Phase
  //--------------------------------------------------------
  task run_phase (uvm_phase phase);
    super.run_phase(phase);
    `uvm_info("DRIVER_CLASS", "Run Phase!", UVM_HIGH)

//4. driver class template (1.0) change  :
// what does a driver do? A driver drives the  logic that we want to use to simulate the DUT.
// let's just drive the inputs to the interface with this handle (vif)
// What we want to do is:  forever run this driver task until the
// simulation ends. In other words, forever keep driving these transactions.
// And what do we want to drive?  We want to drive this packet which is called "item".
// Logic
forever begin
    // Create "item"
      item = SimpleDUT_sequence_item::type_id::create("item");

      // then we use the ports that we need to declare in the agent and we will call it seq_item_port)
      // we just use this port inside the driver to get the sequences from the sequencer and to do that we use       
      // this method:  get_next_item

      seq_item_port.get_next_item(item);

//5. driver class template (1.0) change  :
  // Logic: Our code of the driver here
  // In order to write the driver, it's easier to implement the code directly as a normal testbench and observe its behavior through waveforms.
// Further we will reuse the normal testbench code to implement here in the run phase and lets assume that our driver code will be captured in task "drive" taking an argument:  the transaction "item".
//  We can just create a task that can drive the items although it's not necessary to create create this
  // task you can just easily write down here  whatever the logic is.
  // On the other hand writing a task is an
  // easy way so that in case you need to change something in driver class or
  // drive method; this way it does not affect the Run phase, and the Run phase is the most important phase
  // in driver class.

      drive(item);

  //6. driver class template (1.0) change  :
// after doing or driving something we can just call seq_item_port.item_done()
// indicating that we are done driving the item and we are ready to move on to get next item
      seq_item_port.item_done();
    end

    endtask: run_phase

//7. driver class template(1.0) change  :
// Here  is task "drive" and it also using the item object that is passed by reference
// We want to drive the packet transaction on the interface
// but we want to do it only on the positive edge of the clock.
// Where is that clock,  where can we access that clock ?
// We can access that clock from virtual interface handle that we already have
// and we use non-blocking assignments although it's not necessary though.
//--------------------------------------------------------
  //[Method] Drive
  //--------------------------------------------------------
  task drive(SimpleDUT_sequence_item item);
    // e.g. for our "dummy" DUT
    // @(posedge vif.clock);
    // vif.reset <= item.reset;
  //  vif.din_i <= item.din_i;
  endtask: drive


endclass: SimpleDUT_driver


Monitor class  template evolution

Monitor class  template 1.0 ( one more Component class )


class SimpleDUT_monitor extends uvm_monitor;
  `uvm_component_utils(SimpleDUT_monitor)

 
  //--------------------------------------------------------
  //Constructor
  //--------------------------------------------------------
  function new(string name = "SimpleDUT_monitor", uvm_component parent);
    super.new(name, parent);
    `uvm_info("MONITOR_CLASS", "Inside Constructor!", UVM_HIGH)

  endfunction: new


  //--------------------------------------------------------
  //Build Phase
  //--------------------------------------------------------
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    `uvm_info("MONITOR_CLASS", "Build Phase!", UVM_HIGH)

endfunction: build_phase


  //--------------------------------------------------------
  //Connect Phase
  //--------------------------------------------------------
  function void connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    `uvm_info("MONITOR_CLASS", "Connect Phase!", UVM_HIGH)

  endfunction: connect_phase


  //--------------------------------------------------------
  //Run Phase
  //--------------------------------------------------------
  task run_phase (uvm_phase phase);
    super.run_phase(phase);
    `uvm_info("MONITOR_CLASS", "Run Phase!", UVM_HIGH)

  // Logic

    endtask: run_phase

endclass: SimpleDUT_monitor


Monitor class  template 2.0 ( one more Component class: file monitor.sv )

The monitor is a self-contained model that observes the communication of the DUT with the testbench. At most, it should observe the outputs of the design and, in case of not respecting the protocol’s rules, the monitor must return an error. The monitor is a passive component, it doesn’t drive any signals into the DUT.

class SimpleDUT_monitor extends uvm_monitor;
  `uvm_component_utils(SimpleDUT_monitor)

//1. monitor class template (1.0) change : As in the driver already explained, the monitor also need access to DUT interface 
virtual SimpleDUT_interface vif;

// //3. monitor class template(1.0) change : In the monitor class we are sampling from the interface
// also we will need a name of the handle to the item class that we are going to use in the monitor.
  SimpleDUT_sequence_item item;

// 6. monitor class template change (1.0) : One thing we need to see is how are we going to connect the scoreboard and
// monitor so it is going to be done  just like how we connected the driver and sequencer

// Note* a friendly reminder from agent.sv how we connected the driver and sequence:
// drv.seq_item_port.connect(seqr.seq_item_export);

// driver and sequencer had these ports ( e.g. seq_item_port.connect)  by default but monitor and
// scoreboard does not have them so we need to create those ports with uvm_analysis_port.

// syntax is something like this:  uvm_analysis_port
// and then we pass the parameter to say what kind of sequence item we are going to drive through these ports.
// Basically we say that we are going to drive packet of type SimpleDUT_sequence_item and
// then we can name the port as "monitor_port"
// and then we also need to create monitor_port for example in build phase
// we need to pass two arguments first is the name of the port and second is the parent
uvm_analysis_port #(SimpleDUT_sequence_item) monitor_port;


 
  //--------------------------------------------------------
  //Constructor
  //--------------------------------------------------------
  function new(string name = "SimpleDUT_monitor", uvm_component parent);
    super.new(name, parent);
    `uvm_info("MONITOR_CLASS", "Inside Constructor!", UVM_HIGH)

  endfunction: new


  //--------------------------------------------------------
  //Build Phase
  //--------------------------------------------------------
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    `uvm_info("MONITOR_CLASS", "Build Phase!", UVM_HIGH)

//2. monitor class template (1.0) change : As in the driver already explained, the monitor also need access to DUT interface
  if(!(uvm_config_db #(virtual SimpleDUT_interface)::get(this, "*", "vif", vif))) begin
      `uvm_error("MONITOR_CLASS", "Failed to get VIF from config DB!")
end

// 7. monitor class template change :
// We need to create monitor_port for example in build phase
// we need to pass two arguments first is the name of the port and second is the parent
monitor_port = new("monitor_port", this);

endfunction: build_phase


  //--------------------------------------------------------
  //Connect Phase
  //--------------------------------------------------------
  function void connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    `uvm_info("MONITOR_CLASS", "Connect Phase!", UVM_HIGH)

  endfunction: connect_phase


  //--------------------------------------------------------
  //Run Phase
  //--------------------------------------------------------
  task run_phase (uvm_phase phase);
    super.run_phase(phase);
    `uvm_info("MONITOR_CLASS", "Run Phase!", UVM_HIGH)
  //4. monitor class template(1.0) change :
// What we want to do is:  forever run this until the
// simulation ends in other words forever keep  monitoring  these transactions.
// We want to sample both the input and the output.
// Here I am using blocking because it is a general practice to use non-blocking for driver and blocking for
// monitor class but if you use otherwise that is also completely fine.
// Logic
forever begin
// 5. monitor class template(1.0) change : creation of a transaction
// to store sampled data ( e.g. on each clock tick) and the transaction is passed to scoreboard
      item = SimpleDUT_sequence_item::type_id::create("item");
    // e.g. monitoring logic for our dummy DUT
    //  wait(!vif.reset);
   
      //sample inputs
  //   @(posedge vif.clock);
  //    item.din_i = vif.din_i;
      //sample output
    //  // @(posedge vif.clock);
  //   item.dout_o = vif.dout_o;
   
      // 8. monitor class template change:  send item to scoreboard through the port: use method "write"
      // to write "item" to scoreboard using port "monitor_port"
      //  we also need to implement this "write" method inside the scoreboard
      @(posedge vif.clock);
      monitor_port.write(item);
    end

    endtask: run_phase

endclass: SimpleDUT_monitor


Scoreboard template ( A Component class file : scoreboard.sv )

class SimpleDUT_scoreboard extends uvm_test;
  `uvm_component_utils(SimpleDUT_scoreboard)
//1. Change to UVM scoreboard component class template 1.0:
// Similar to the monitor class,  we need to do something similar for the scoreboard port.
// Now the caveat is that driver will be sending packets, so in drivers case the port will be a transmitter port (uses uvm_analysis_port)
// Scoreboard will need a receiver port so Scoreboard port will be a receiver port ( uses: uvm_analysis_imp ).
// uvm_analysis_imp has two parameters:  one is the sequence item, and here  we are going to use
// SimpleDUT_sequence_item and the other is the name of the scoreboard (SimpleDUT_scoreboard).
// Finally we can call this port as  scoreboard_port.
uvm_analysis_imp #(SimpleDUT_sequence_item, SimpleDUT_scoreboard) scoreboard_port;

//4. Change to UVM scoreboard component class template 1.0:
  //  let's say monitor sends or writes
  // this item in the port and scoreboard will receive it so what we need to do is
  // make a database or store that item or packets let's say  we want to run
  // the sequence 100 times and we want to save all those 100 transactions so we
  // can use a data structure to save all the packets. Easiest data structure
  // is the queue that we can use here so that it becomes a sort of a fifo.
  // As a conclusion here we are going to  create a queue  of type
  // sequence item and we can call this queue:  transactions.
// transactions: This is the name given to the array of SimpleDUT_sequence_item objects
// It is a variable name that can be used to access individual elements within the array.
// The [$] syntax allows the array size to be determined dynamically at runtime, as opposed to a fixed size declared at compile-time.
SimpleDUT_sequence_item transactions[$];

  //--------------------------------------------------------
  //Constructor
  //--------------------------------------------------------
  function new(string name = "SimpleDUT_scoreboard", uvm_component parent);
    super.new(name, parent);
    `uvm_info("SCB_CLASS", "Inside Constructor!", UVM_HIGH)

  endfunction: new


  //--------------------------------------------------------
  //Build Phase
  //--------------------------------------------------------
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    `uvm_info("SCB_CLASS", "Build Phase!", UVM_HIGH)

    //2. Change to UVM scoreboard component class template
    //   scoreboard_port we need to construct/create in for example build phase
    scoreboard_port = new("scoreboard_port", this); 

endfunction: build_phase


  //--------------------------------------------------------
  //Connect Phase
  //--------------------------------------------------------
  function void connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    `uvm_info("SCB_CLASS", "Connect Phase!", UVM_HIGH)

  endfunction: connect_phase


  // 3. Change to UVM scoreboard component class template
  // We also need to implement the "write" method ( from monitor class line: monitor_port.write(item);):
  // this is the most important part of the scoreboard:  write method.
  // The write method determines what is going to happen when monitor sends the item in the
  // scoreboard.
  // We are implementing this method here and that's why the name over
  // here is:  uvm_analysis_imp #((SimpleDUT_sequence_item, SimpleDUT_scoreboard) scoreboard_port; uvm_analysis_imp stands for implementation).  
  // The port name is UVM analysis implementation (uvm_analysis_imp) because we are
  // implementing this write method in this class.
  // We also need to pass "item"  as a parameter.
  //--------------------------------------------------------
  //Write Method
  //--------------------------------------------------------
  function void write(SimpleDUT_sequence_item item);
// Make a database or store that item or packets.
// use this transactions.push_back what do we want to push is this item so
// one by one whenever this method is called. We are just pushing this item into "transactions"
// array
    transactions.push_back(item);

// in the Run phase we will do something about those transactions maybe compare or
// something like that


  endfunction: write

//--------------------------------------------------------
  //Run Phase
  //--------------------------------------------------------
  task run_phase (uvm_phase phase);
    super.run_phase(phase);
    `uvm_info("SCB_CLASS", "Run Phase!", UVM_HIGH)

      //5. Change to UVM scoreboard component class template
      // In the Run phase like in other run phases we just want to keep on  running this forever until the
    // simulation ends
    forever begin
      /*
      // get the packet
      // generate expected value
      // compare it with actual value
      // score the transactions accordingly
      */
      SimpleDUT_sequence_item curr_trans;

// we need to wait for until the transaction queue size becomes greater than 0
// we don't want to run this task of popping up the data from fifo if we have not received any transactions yet.
      wait((transactions.size() != 0));

// copy/ pop it from the fifo "transactions" only one transaction at a time and then we play with that in the
// forever loop
      curr_trans = transactions.pop_front();

//"compare" is task that take as argument that one piece of data we just popped from the fifo
      compare(curr_trans);
   
    end

    endtask: run_phase

//6. Change to UVM scoreboard component class template
// Adding compare task:
  //--------------------------------------------------------
  //Compare : Generate Expected Result and Compare with Actual
  //--------------------------------------------------------
  task compare(SimpleDUT_sequence_item curr_trans);
    logic [3:0] expected;
    logic [3:0] actual;
 
  expected = curr_trans.din_i;
  actual   = curr_trans.dout_o;
 
    if(actual != expected) begin
      `uvm_error("COMPARE", $sformatf("Transaction failed! ACT=%d, EXP=%d", actual, expected))
    end
    else begin
      `uvm_info("COMPARE", $sformatf("Transaction Passed! ACT=%d, EXP=%d", actual, expected), UVM_LOW)
    end
 
  endtask: compare

endclass: SimpleDUT_scoreboard


Agent class template evolution

Agent class template 1.0 ( once more Component class ) 

Let's create the agent class template which will be same as the environment and test class templates ( and in the same fashion all other necessary COMPONENT classes ) 

  • The agent class template  will have  three instances  class templates:  driver sequencer and monitor



class SimpleDUT_agent extends uvm_agent;
  `uvm_component_utils(SimpleDUT_agent)

 
  //--------------------------------------------------------
  //Constructor
  //--------------------------------------------------------
  function new(string name = "SimpleDUT_agent", uvm_component parent);
    super.new(name, parent);
    `uvm_info("AGENT_CLASS", "Inside Constructor!", UVM_HIGH)

  endfunction: new


  //--------------------------------------------------------
  //Build Phase
  //--------------------------------------------------------
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    `uvm_info("AGENT_CLASS", "Build Phase!", UVM_HIGH)

endfunction: build_phase


  //--------------------------------------------------------
  //Connect Phase
  //--------------------------------------------------------
  function void connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    `uvm_info("AGENT_CLASS", "Connect Phase!", UVM_HIGH)

  endfunction: connect_phase


  //--------------------------------------------------------
  //Run Phase
  //--------------------------------------------------------
  task run_phase (uvm_phase phase);
    super.run_phase(phase);
    `uvm_info("AGENT_CLASS", "Run Phase!", UVM_HIGH)

  // Logic

    endtask: run_phase

endclass: SimpleDUT_agent



Agent class template 2.0 ( once more a Component class: file agent.sv ) 

Let's create the agent class template which will be same as the environment and test class templates ( and in the same fashion all other necessary COMPONENT classes ) 

  • The agent class template  will have  three instances  class templates:  driver sequencer and monitor



class SimpleDUT_agent extends uvm_agent;
  `uvm_component_utils(SimpleDUT_agent)

// according to our block diagram the agent will contain driver, monitor and sequencer so we need to write the code accordingly

  SimpleDUT_driver    drv;
  SimpleDUT_monitor   mon;
  SimpleDUT_sequencer seqr;
 
  SimpleDUT_sequence_item item;
 
  //--------------------------------------------------------
  //Constructor
  //--------------------------------------------------------
  function new(string name = "SimpleDUT_agent", uvm_component parent);
    super.new(name, parent);
    `uvm_info("AGENT_CLASS", "Inside Constructor!", UVM_HIGH)

  endfunction: new


  //--------------------------------------------------------
  //Build Phase
  //--------------------------------------------------------
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    `uvm_info("AGENT_CLASS", "Build Phase!", UVM_HIGH)

// we also need to create the driver, monitor and sequencer inside the build method
    drv = SimpleDUT_driver::type_id::create("drv", this);
    mon = SimpleDUT_monitor::type_id::create("mon", this);
    seqr = SimpleDUT_sequencer::type_id::create("seqr", this);

endfunction: build_phase


  //--------------------------------------------------------
  //Connect Phase
  //--------------------------------------------------------
  function void connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    `uvm_info("AGENT_CLASS", "Connect Phase!", UVM_HIGH)

// In the connect phase we need to connect the sequence item ports of driver and sequencer. This is required
// to exchange the transactions.
// These are the ports ( e.g. seq_item_port) that are by default defined inside the driver and
// sequencer class so this is how we just connect them.
  drv.seq_item_port.connect(seqr.seq_item_export);


  endfunction: connect_phase


  //--------------------------------------------------------
  //Run Phase
  //--------------------------------------------------------
  task run_phase (uvm_phase phase);
    super.run_phase(phase);
    `uvm_info("AGENT_CLASS", "Run Phase!", UVM_HIGH)
// What we want to do is:  forever run this until the
// simulation ends in other words forever keep  monitoring  these transactions.
// forever begin
      item = SimpleDUT_sequence_item::type_id::create("item");
      // Logic of monitoring DUT and storing results in "item"
         
      // to write "item" to scoreboard using port "monitor_port"
      //  we also need to implement this "write" method inside the scoreboard
      mon.monitor_port.write(item);
//   end
    endtask: run_phase

endclass: SimpleDUT_agent




Environment class template evolution

Environment class template 1.0

 Let's create the environment class template which will be same as the test class template ( and in the same fashion all other necessary COMPONENT classes templates ) 

  • As a friendly reminder the  environment class template  will have  instances of agent and scoreboard classes template instances  as it is planned in our UVM verification infrastructure architecture 



class SimpleDUT_env extends uvm_test;
  `uvm_component_utils(SimpleDUT_env)

  //--------------------------------------------------------
  //Constructor
  //--------------------------------------------------------
  function new(string name = "SimpleDUT_env", uvm_component parent);
    super.new(name, parent);
    `uvm_info("ENV_CLASS", "Inside Constructor!", UVM_HIGH)

  endfunction: new

  //--------------------------------------------------------
  //Build Phase
  //--------------------------------------------------------
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    `uvm_info("ENV_CLASS", "Build Phase!", UVM_HIGH)

endfunction: build_phase


  //--------------------------------------------------------
  //Connect Phase
  //--------------------------------------------------------
  function void connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    `uvm_info("ENV_CLASS", "Connect Phase!", UVM_HIGH)

  endfunction: connect_phase


  //--------------------------------------------------------
  //Run Phase
  //--------------------------------------------------------
  task run_phase (uvm_phase phase);
    super.run_phase(phase);
    `uvm_info("ENV_CLASS", "Run Phase!", UVM_HIGH)

  // Logic

    endtask: run_phase

endclass: SimpleDUT_env

 

Environment class template 2.0 ( A Component class: file env.sv)

 Let's create the environment class template which will be same as the test class template ( and in the same fashion all other necessary COMPONENT classes templates ) 

  • As a friendly reminder the  environment class template  will have  instances of agent and scoreboard classes template instances  as it is planned in our UVM verification infrastructure architecture 



class SimpleDUT_env extends uvm_test;
  `uvm_component_utils(SimpleDUT_env)
// according to our block diagram the  environment class will contain agent, then  agent will contain driver monitor and sequencer so we need to write the code accordingly

// Here in environment class, let's instantiate a SimpleDUT_agent object named env and initialize it using the create method, passing a unique name and the current environment  class as the parent. This allows the  environment class to interact with and control the instantiated during test execution.

// 1. change of environment class based on environment class template:  so let's instantiate the
// agent class inside the environment (SimpleDUT_agent agnt)

// This is just the instantiation
// This line declares a variable named env of type alu_agent. This variable will be used to hold an instance of the SimpleDUT__agent class, which represents the agent in the environment.

SimpleDUT_agent agnt;

// 4. change of environment class based on environment class template:
// In order to connect monitor and  scoreboard we will always go to a higher component then these two.
// Since agent and scoreboard lie on the same hierarchy while driver and monitor and sequencer lie inside the agent so now I need to go up a hierarchy above agent and scoreboard which is the environment.
// In  the environment we can connect those ports in the connect phase of environment:
// To connect the scoreboard later we need to instantiate it here
SimpleDUT_scoreboard scb;




  //--------------------------------------------------------
  //Constructor
  //--------------------------------------------------------
  function new(string name = "SimpleDUT_env", uvm_component parent);
    super.new(name, parent);
    `uvm_info("ENV_CLASS", "Inside Constructor!", UVM_HIGH)

  endfunction: new

  //--------------------------------------------------------
  //Build Phase
  //--------------------------------------------------------
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    `uvm_info("ENV_CLASS", "Build Phase!", UVM_HIGH)

// 2. change of environment class based on environment class template: and we also need to create an agent inside the build method( agnt = SimpleDUT__agent::type_id::create("agnt", this);)
// This line creates an instance of the agent_env class using the create method
// this is a component class we need to pass two parameters:  one is the "agnt" a name itself
// so whatever we want the agnt to be called to be known as in the testbench and
// you also need to pass "this" as a parameter this will specify who is the parent of agent
// so it's just like SimpleDUT_env contains the agent class

// SimpleDUT_agent::type_id is used to obtain the type identifier of the SimpleDUT__env class. The type identifier is a unique identifier generated by the UVM framework for each class. It allows for dynamic object creation and type checking.

// The create method takes two arguments:
// "agnt": This is the desired name for the created object. It is passed as a string.this: This is a reference to the current object or component. In this case, it refers to the test class where this code is executed.
// The this pointer is used to specify the parent of the created object, which helps establish the hierarchical relationship between components in the UVM environment.
agnt = SimpleDUT_agent::type_id::create("agnt", this);
// 5. change of environment class based on environment class template:
// same as 2. Change, do  the same for scoreboard
scb = SimpleDUT_scoreboard::type_id::create("scb", this);
   
endfunction: build_phase

 
  //--------------------------------------------------------
  //Connect Phase
  //--------------------------------------------------------
  function void connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    `uvm_info("ENV_CLASS", "Connect Phase!", UVM_HIGH)

// 3. change of environment class based on environment class template:
//  Since we don't have the monitor handle over here in env.sv
// right we need to access it through agent:  agnt.mon.monitor_port.connect
// and this is how we connect monitor port and scoreboard port
// our monitor will monitor the items uh or sample the items and then  send items to
// scoreboard through the port and scoreboard will do something with it
// To connect the scoreboard here,   we need to instantiate it before  
  agnt.mon.monitor_port.connect(scb.scoreboard_port);


  endfunction: connect_phase

 
  //--------------------------------------------------------
  //Run Phase
  //--------------------------------------------------------
  task run_phase (uvm_phase phase);
    super.run_phase(phase);
    `uvm_info("ENV_CLASS", "Run Phase!", UVM_HIGH)

  // Logic

    endtask: run_phase

endclass: SimpleDUT_env


UVM Test Component template evolution

UVM Test Component template 1.0


// let's create a new class in a file named test.sv
// we can start with naming our class SimpleDUT_test.
// SimpleDUT_test   extends uvm_test class

//COMPONENT class
class SimpleDUT_test extends uvm_test;
  // first thing we need to do in a class
  // is declare the necessary macros that will help it
  // register everything with the UVM classes
  // so we need to call this macro called `uvm_component_utils
  // and we need to pass the same name of the class that we are going to use
  // and that is SimpleDUT_test

  `uvm_component_utils(SimpleDUT_test)

  //  second thing we need to do is write a Constructor
  //  start with: function new();
  //                    endfunction
  // inside brackets of new() two parameters:  first  string name is equal to the
  // name of the class( SimpleDUT_test) and then we also need to write
// uvm_component parent

// Finally basic constructor for UVM classes is:
// function new(string name = "SimpleDUT_test", uvm_component parent);
//    super.new(name, parent);
// endfunction: new

// also keep some display statement so that it becomes easier to debug
//  we will use the default `uvm_info macro that UVM already has
// it has like three parameters first is the tag of the comment that we want to give(  we can just give a
// TEST_CLASS and then we can also use a
// just some comment that we want to pass when  inside this Constructor
// ( "Inside Constructor!")
// and then verbosity level for example UVM_HIGH

       
  //--------------------------------------------------------
  // Constructor
  //--------------------------------------------------------
  function new(string name = "SimpleDUT_test", uvm_component parent);
    super.new(name, parent);
    `uvm_info("TEST_CLASS", "Inside Constructor!", UVM_HIGH)
  endfunction: new

  //and now we need to write the build phase so
  // we do that by using function void

  //--------------------------------------------------------
  // Build Phase
  //--------------------------------------------------------
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    `uvm_info("TEST_CLASS", "Build Phase!", UVM_HIGH)
 
  endfunction: build_phase

// Next is  connect phase which is same similar to build phase
// but we'll just change the syntax

//--------------------------------------------------------
  //Connect Phase
  //--------------------------------------------------------
  function void connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    `uvm_info("TEST_CLASS", "Connect Phase!", UVM_HIGH)

  endfunction: connect_phase

// we also need to write a run phase. There are a lot of phases in a UVM but
// these three are the main phases  with which
// our SimpleDUT could be verified easily

// all the other phases are functions while only the run phase is a task because
// run phase can consume time



//--------------------------------------------------------
  //Run Phase
  //--------------------------------------------------------
  task run_phase (uvm_phase phase);
    super.run_phase(phase);
    `uvm_info("TEST_CLASS", "Run Phase!", UVM_HIGH)
  endtask: run_phase


endclass: SimpleDUT_test


UVM Test Component template 2.0

it will have two purposes:

  • Create the env block

  • Connect the sequencer to the sequence


class SimpleDUT_test extends uvm_test;
  `uvm_component_utils(SimpleDUT_test)

// according to our block diagram the test component
// will contain environment class environment class will contain agent agent will contain driver Monitor and
// sequencer so we need to write the code accordingly

// Here in test class let's instantiate an SimpleDUT_env object named env and initialize it using the create method, passing a unique name and the current test class as the parent. This allows the test class to interact with and control the instantiated environment during test execution.

// 1. change of test class based on test class template:  so let's instantiate the
// environment class inside the test SimpleDUT_env env)

// This is just the instantiation
// This line declares a variable named env of type SimpleDUT__env. This variable will be used to hold an instance of the SimpleDUT__env class, which represents the test environment.
  SimpleDUT_env env;

// 4. change of test class based on test class template:  in order to start our sequences in the run phase
//  we need the sequences
//  instances i so we have two sequences that we created one is the
// base sequence and another one is the test sequence so let's just create handles
// Instantiation two sequences
  SimpleDUT_base_sequence reset_seq;
  SimpleDUT_test_sequence test_seq;

       
  //--------------------------------------------------------
  // Constructor
  //--------------------------------------------------------
  function new(string name = "SimpleDUT_test", uvm_component parent);
    super.new(name, parent);
    `uvm_info("TEST_CLASS", "Inside Constructor!", UVM_HIGH)
  endfunction: new

  //--------------------------------------------------------
  // Build Phase
  //--------------------------------------------------------
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    `uvm_info("TEST_CLASS", "Build Phase!", UVM_HIGH)

// 2. change of test class based on test class template: and we also need to create env inside the build method( env = SimpleDUT_env::type_id::create("env", this);)
// This line creates an instance of the SimpleDUT_env class using the create method
// this is a component class we need to pass two parameters:  one is the "env" a name itself
// so whatever we want the env to be called to be known as in the testbench and
// you also need to pass "this" as a parameter this will specify who is the parent of environment
// so it's just like SimpleDUT_test contains the environment class

// SimpleDUT_env::type_id is used to obtain the type identifier of the SimpleDUT_env class. The type identifier is a unique identifier generated by the UVM framework for each class. It allows for dynamic object creation and type checking.

// The create method takes two arguments:
// "env": This is the desired name for the created object. It is passed as a string.this: This is a reference to the current object or component. In this case, it refers to the test class where this code is executed.
// The this pointer is used to specify the parent of the created object, which helps establish the hierarchical relationship between components in the UVM environment.

    env = SimpleDUT_env::type_id::create("env", this);

   
  endfunction: build_phase

//--------------------------------------------------------
  //Connect Phase
  //--------------------------------------------------------
  function void connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    `uvm_info("TEST_CLASS", "Connect Phase!", UVM_HIGH)

  endfunction: connect_phase
  //--------------------------------------------------------
  //Run Phase
  //--------------------------------------------------------
  task run_phase (uvm_phase phase);
    super.run_phase(phase);
    `uvm_info("TEST_CLASS", "Run Phase!", UVM_HIGH)
   
// 3. change of test class based on test class template: run phase of test class 
// will start the sequences that it wants to run
// so to do that one important thing test class we need to do in test class is
// we need to say that hey I'm going to run my test
// we use the phase.raise_objection
// and we also need to after we are done
// with the test we also need to phase.drop_objection so that the test can end
      phase.raise_objection(this);
// 5. change of test class based on test class template: When starting  my reset sequence on sequencer first we
// specify a path of that sequencer is something like env agent sequencer
// ( env.agnt.seqr) because our sequence  is lying inside the agent which is lying inside the environment.
// So we specify that path over here so what this will do is to  start this reset sequence on this
// sequencer which is lying inside this agent which is in turn lying inside this environment because there can be
// multiple environments and multiple agents and also multiple sequencers so we are specifying which sequencer we want to run our // sequence on

    //reset_seq
    // Constructing the reset sequence
    reset_seq = SimpleDUT_base_sequence::type_id::create("reset_seq");
    // Starting the reset sequence on the sequencer on path env.agnt.seqr
    reset_seq.start(env.agnt.seqr);
    #10;
// e.g. repeat test sequence 100 times
//    repeat(100) begin
      //test_seq
      // Constructing the test sequence 
      test_seq = SimpleDUT_test_sequence::type_id::create("test_seq");
      // Starting the test sequence on the sequencer on path env.agnt.seqr
      test_seq.start(env.agnt.seqr);
      #10;
//    end
   
    phase.drop_objection(this);


  endtask: run_phase


endclass: SimpleDUT_test



 testbench.sv (top testbench module )


`timescale 1ns/1ns

// This is where the basic UVM necessary classes are included that we need to get started with our top module.
// The first thing, whenever you start with a UVM test bench, you need to define some UVM based packages so we need to import them here using  this  basic syntax
import uvm_pkg::*;

// we also need to include some UVM macros
`include "uvm_macros.svh"

//--------------------------------------------------------
//Include Files
//--------------------------------------------------------
// The way we include the classes in the top testbench is that the order of the included classes is very important.
// The first class that you are going to use should be the first one included and so on.
// e.g. that is the reason why we start with sequence item class etc.etc.

`include "interface.sv"
`include "sequence_item.sv"
`include "sequence.sv"
`include "sequencer.sv"
`include "driver.sv"
`include "monitor.sv"
`include "agent.sv"
`include "scoreboard.sv"
`include "env.sv"
`include "test.sv"

module top;

// the clock for DUT is generated  from the top testbench module so let's declare a signal clock here
logic clock;

  SimpleDUT_interface intf(.clock(clock));

  SimpleDUT dut(
    .clk(intf.clock),
    .reset(intf.reset),
    .din_i(intf.din_i),
    .dout_o(intf.dout_o) );

//--------------------------------------------------------
  //Interface Setting
  //--------------------------------------------------------
  initial begin
// In SystemVerilog, the uvm_config_db is a utility provided by the Universal Verification Methodology (UVM) library. It allows you to set configuration values in a centralized database that can be accessed by different components of your testbench
// uvm_config_db is the class that provides the configuration database functionality in UVM
// In the given example, the uvm_config_db::set call sets the configuration value "vif" with the value intf (an instance of SimpleDUT_interface) in the global context for all instances in the hierarchy. This allows other components of the testbench to access and use this configuration value when needed.
/*
#(virtual SimpleDUT_interface): This is a type parameter passed to the uvm_config_db class, specifying the data type of the configuration value. In this case, it is a virtual interface type called SimpleDUT_interface.
set: This is a static method of the uvm_config_db class used to set a configuration value in the database.
null: This represents the context in which the configuration value is being set. In this case, it is set to null, indicating a global context.
"*": This is the hierarchical path pattern specifying the scope at which the configuration value is applicable. In this case, "*" indicates that the configuration applies to all instances in the hierarchy.
"vif": This is the name of the configuration field being set. It is a user-defined identifier that represents a specific configuration parameter or object in the database.
intf: This is the value being set for the configuration field "vif". It represents an instance of the SimpleDUT_interface virtual interface.
*/

    uvm_config_db #(virtual SimpleDUT_interface)::set(null, "*", "vif", intf );
    //-- Refer: https://www.synopsys.com/content/dam/synopsys/services/whitepapers/hierarchical-testbench-configuration-using-uvm.pdf
  end

// The last thing to add  in our top testbench  is:  start the test  and that is done by calling  run test task in.
// The top module should take care of our test so when you reach this stage.

//Start The Test
  //--------------------------------------------------------
  initial begin
// This line executes a UVM built-in task run_test(). The purpose of this task is to start the specified test from the UVM test library. In this case, it starts the test named "SimpleDUT_test".
    run_test("SimpleDUT_test");
  end
 
  //--------------------------------------------------------
  //Clock Generation
  //--------------------------------------------------------
// Since the clock for DUT is generated  from the top testbench module , let's generate the clock here
  initial begin
    clock = 0;
    #5;
    forever begin
      clock = ~clock;
      #2;
    end
  end
  //--------------------------------------------------------
  //Maximum Simulation Time
  //--------------------------------------------------------
// let's also keep a safety check which terminates our simulation if a given number of clock Cycles X exceeds so we will keep it to maybe 5000ns
  initial begin
    #5000;
    $display("Sorry! Ran out of clock cycles!");
    $finish();
  end

 
  //--------------------------------------------------------
  //Generate Waveforms
  //--------------------------------------------------------
  // if you want to see the waveforms you can also use the dump file  command to  tell the simulator to dump variable values so that
  // you can inspect  the waveforms after verification/simulation

  initial begin
    $dumpfile("d.vcd");
    $dumpvars();
  end
 
endmodule: top


Compilation

[2023-07-21 08:52:49 UTC] vcs -licqueue '-timescale=1ns/1ns' '+vcs+flush+all' '+warn=all' '-sverilog' +incdir+$UVM_HOME/src $UVM_HOME/src/uvm.sv $UVM_HOME/src/dpi/uvm_dpi.cc -CFLAGS -DVCS design.sv testbench.sv && ./simv +vcs+lic+wait '+UVM_VERBOSITY=UVM_HIGH'


Chronologic VCS (TM)

Version S-2021.09 -- Fri Jul 21 04:52:50 2023


Parsing design file 'design.sv'

Parsing design file 'testbench.sv'


Note-[SV-LCM-PPWI] Package previously wildcard imported

testbench.sv, 5

$unit

Package 'uvm_pkg' already wildcard imported.

Ignoring uvm_pkg::*

See the SystemVerilog LRM(1800-2005), section 19.2.1.


Parsing included file '/apps/vcsmx/vcs/S-2021.09//etc/uvm-1.2/src/uvm_macros.svh'.

Back to file 'testbench.sv'.

Parsing included file 'interface.sv'.

Back to file 'testbench.sv'.

Parsing included file 'sequence_item.sv'.

Back to file 'testbench.sv'.

Parsing included file 'sequence.sv'.

Back to file 'testbench.sv'.

Parsing included file 'sequencer.sv'.

Back to file 'testbench.sv'.

Parsing included file 'driver.sv'.

Back to file 'testbench.sv'.

Parsing included file 'monitor.sv'.

Back to file 'testbench.sv'.

Parsing included file 'agent.sv'.

Back to file 'testbench.sv'.

Parsing included file 'scoreboard.sv'.

Back to file 'testbench.sv'.

Parsing included file 'env.sv'.

Back to file 'testbench.sv'.

Parsing included file 'test.sv'.

Back to file 'testbench.sv'.

Top Level Modules:

top

TimeScale is 1 ns / 1 ns



Starting vcs inline pass...


6 modules and 0 UDP read.

recompiling package vcs_paramclassrepository

recompiling package _vcs_DPI_package

recompiling package uvm_pkg

recompiling module SimpleDUT

recompiling module SimpleDUT_interface

recompiling module top

All of 6 modules done

rm -f _cuarc*.so _csrc*.so pre_vcsobj_*.so share_vcsobj_*.so

g++ -w -pipe -m32 -DVCS -O -I/apps/vcsmx/vcs/S-2021.09/include -c /apps/vcsmx/vcs/S-2021.09//etc/uvm-1.2/src/dpi/uvm_dpi.cc

gcc -w -pipe -m32 -DVCS -O -I/apps/vcsmx/vcs/S-2021.09/include -c -o uM9F1_0x2aB.o uM9F1_0x2aB.c

if [ -x ../simv ]; then chmod a-x ../simv; fi

g++ -o ../simv -m32 -m32 -rdynamic -Wl,-rpath='$ORIGIN'/simv.daidir -Wl,-rpath=./simv.daidir -Wl,-rpath=/apps/vcsmx/vcs/S-2021.09/linux/lib -L/apps/vcsmx/vcs/S-2021.09/linux/lib -Wl,-rpath-link=./ -Wl,--no-as-needed uvm_dpi.o objs/amcQw_d.o _416_archive_1.so SIM_l.o uM9F1_0x2aB.o rmapats_mop.o rmapats.o rmar.o rmar_nd.o rmar_llvm_0_1.o rmar_llvm_0_0.o -lvirsim -lerrorinf -lsnpsmalloc -lvfs -lvcsnew -lsimprofile -luclinative /apps/vcsmx/vcs/S-2021.09/linux/lib/vcs_tls.o -Wl,-whole-archive -lvcsucli -Wl,-no-whole-archive ./../simv.daidir/vc_hdrs.o /apps/vcsmx/vcs/S-2021.09/linux/lib/vcs_save_restore_new.o /apps/vcsmx/vcs/S-2021.09/linux/lib/ctype-stubs_32.a -ldl -lc -lm -lpthread -ldl

../simv up to date

CPU time: 9.754 seconds to compile + .447 seconds to elab + .568 seconds to link

Chronologic VCS simulator copyright 1991-2021

Contains Synopsys proprietary information.

Compiler version S-2021.09; Runtime version S-2021.09; Jul 21 04:53 2023

UVM_INFO /apps/vcsmx/vcs/S-2021.09//etc/uvm-1.2/src/base/uvm_root.svh(402) @ 0: reporter [UVM/RELNOTES]

----------------------------------------------------------------

UVM-1.2.Synopsys

(C) 2007-2014 Mentor Graphics Corporation

(C) 2007-2014 Cadence Design Systems, Inc.

(C) 2006-2014 Synopsys, Inc.

(C) 2011-2013 Cypress Semiconductor Corp.

(C) 2013-2014 NVIDIA Corporation

----------------------------------------------------------------


*********** IMPORTANT RELEASE NOTES ************


You are using a version of the UVM library that has been compiled

with `UVM_NO_DEPRECATED undefined.

See http://www.eda.org/svdb/view.php?id=3313 for more details.


You are using a version of the UVM library that has been compiled

with `UVM_OBJECT_DO_NOT_NEED_CONSTRUCTOR undefined.

See http://www.eda.org/svdb/view.php?id=3770 for more details.


(Specify +UVM_NO_RELNOTES to turn off this notice)


UVM_INFO test.sv(31) @ 0: uvm_test_top [TEST_CLASS] Inside Constructor!

UVM_INFO @ 0: reporter [RNTST] Running test SimpleDUT_test...

UVM_INFO test.sv(39) @ 0: uvm_test_top [TEST_CLASS] Build Phase!

UVM_INFO env.sv(30) @ 0: uvm_test_top.env [ENV_CLASS] Inside Constructor!

UVM_INFO env.sv(39) @ 0: uvm_test_top.env [ENV_CLASS] Build Phase!

UVM_INFO agent.sv(17) @ 0: uvm_test_top.env.agnt [AGENT_CLASS] Inside Constructor!

UVM_INFO scoreboard.sv(31) @ 0: uvm_test_top.env.scb [SCB_CLASS] Inside Constructor!

UVM_INFO agent.sv(27) @ 0: uvm_test_top.env.agnt [AGENT_CLASS] Build Phase!

UVM_INFO driver.sv(29) @ 0: uvm_test_top.env.agnt.drv [DRIVER_CLASS] Inside Constructor!

UVM_INFO monitor.sv(35) @ 0: uvm_test_top.env.agnt.mon [MONITOR_CLASS] Inside Constructor!

UVM_INFO sequencer.sv(13) @ 0: uvm_test_top.env.agnt.seqr [SEQUENCER_CLASS] Inside Constructor!

UVM_INFO driver.sv(39) @ 0: uvm_test_top.env.agnt.drv [DRIVER_CLASS] Build Phase!

UVM_INFO monitor.sv(45) @ 0: uvm_test_top.env.agnt.mon [MONITOR_CLASS] Build Phase!

UVM_INFO sequencer.sv(23) @ 0: uvm_test_top.env.agnt.seqr [SEQUENCER_CLASS] Build Phase!

UVM_INFO scoreboard.sv(41) @ 0: uvm_test_top.env.scb [SCB_CLASS] Build Phase!

UVM_INFO driver.sv(58) @ 0: uvm_test_top.env.agnt.drv [DRIVER_CLASS] Connect Phase!

UVM_INFO monitor.sv(65) @ 0: uvm_test_top.env.agnt.mon [MONITOR_CLASS] Connect Phase!

UVM_INFO sequencer.sv(33) @ 0: uvm_test_top.env.agnt.seqr [SEQUENCER_CLASS] Connect Phase!

UVM_INFO agent.sv(42) @ 0: uvm_test_top.env.agnt [AGENT_CLASS] Connect Phase!

UVM_INFO scoreboard.sv(55) @ 0: uvm_test_top.env.scb [SCB_CLASS] Connect Phase!

UVM_INFO env.sv(66) @ 0: uvm_test_top.env [ENV_CLASS] Connect Phase!

UVM_INFO test.sv(64) @ 0: uvm_test_top [TEST_CLASS] Connect Phase!

UVM_INFO driver.sv(68) @ 0: uvm_test_top.env.agnt.drv [DRIVER_CLASS] Run Phase!

UVM_INFO monitor.sv(75) @ 0: uvm_test_top.env.agnt.mon [MONITOR_CLASS] Run Phase!

UVM_INFO agent.sv(59) @ 0: uvm_test_top.env.agnt [AGENT_CLASS] Run Phase!

UVM_INFO scoreboard.sv(91) @ 0: uvm_test_top.env.scb [SCB_CLASS] Run Phase!

UVM_INFO scoreboard.sv(136) @ 0: uvm_test_top.env.scb [COMPARE] Transaction Passed! ACT= x, EXP= x

UVM_INFO env.sv(86) @ 0: uvm_test_top.env [ENV_CLASS] Run Phase!

UVM_INFO test.sv(72) @ 0: uvm_test_top [TEST_CLASS] Run Phase!

UVM_INFO sequence.sv(17) @ 0: reporter@@reset_seq [BASE_SEQ] Inside Constructor!

UVM_INFO sequence.sv(27) @ 0: uvm_test_top.env.agnt.seqr@@reset_seq [BASE_SEQ] Inside body task!

UVM_INFO scoreboard.sv(136) @ 5: uvm_test_top.env.scb [COMPARE] Transaction Passed! ACT= x, EXP= x

UVM_INFO scoreboard.sv(136) @ 9: uvm_test_top.env.scb [COMPARE] Transaction Passed! ACT= x, EXP= x

UVM_INFO sequence.sv(17) @ 10: reporter@@test_seq [BASE_SEQ] Inside Constructor!

UVM_INFO sequence.sv(62) @ 10: reporter@@test_seq [TEST_SEQ] Inside Constructor!

UVM_INFO sequence.sv(70) @ 10: uvm_test_top.env.agnt.seqr@@test_seq [TEST_SEQ] Inside body task!

UVM_INFO scoreboard.sv(136) @ 13: uvm_test_top.env.scb [COMPARE] Transaction Passed! ACT= x, EXP= x

UVM_INFO scoreboard.sv(136) @ 17: uvm_test_top.env.scb [COMPARE] Transaction Passed! ACT= x, EXP= x

UVM_INFO /apps/vcsmx/vcs/S-2021.09//etc/uvm-1.2/src/base/uvm_objection.svh(1276) @ 20: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase

UVM_INFO /apps/vcsmx/vcs/S-2021.09//etc/uvm-1.2/src/base/uvm_report_server.svh(904) @ 20: reporter [UVM/REPORT/SERVER]

--- UVM Report Summary ---


** Report counts by severity

UVM_INFO : 40

UVM_WARNING : 0

UVM_ERROR : 0

UVM_FATAL : 0

** Report counts by id

[AGENT_CLASS] 4

[BASE_SEQ] 3

[COMPARE] 5

[DRIVER_CLASS] 4

[ENV_CLASS] 4

[MONITOR_CLASS] 4

[RNTST] 1

[SCB_CLASS] 4

[SEQUENCER_CLASS] 3

[TEST_CLASS] 4

[TEST_DONE] 1

[TEST_SEQ] 2

[UVM/RELNOTES] 1


$finish called from file "/apps/vcsmx/vcs/S-2021.09//etc/uvm-1.2/src/base/uvm_root.svh", line 527.

$finish at simulation time 20

V C S S i m u l a t i o n R e p o r t

Time: 20 ns

CPU Time: 0.640 seconds; Data structure size: 0.3Mb

Fri Jul 21 04:53:02 2023





© 2023 ASIC Stoic. All rights reserved.