uvm_heartbeat

UVM HeartBeat

Heartbeats provide a way for environments to easily ensure that their descendants are alive. or in other words, the uvm_heart beat catches the deadlock or hang states of the verification components.

How uvm_heartbeat work?

UVM_heartbeat watches for an activity in the test bench and if it finds that there is no activity in the specified interval of time, then uvm_heratbeat issue a fatal message which leads to the end of the simulation.

What are the benefits of using the uvm_heartbeat?

As mentioned earlier uvm_heartbeat identifies the simulation hang situation and terminates the run,

  • which will help in identifying the component which is cause for deadlock
  • saves the simulation time and releases the resources by early termination of simulation

How to use the uvm_heartbeat?

It involves the below steps,

  • Create the heart_beat object
  • Set the components to be monitored
  • trigger the heartbeat monitor

To make the above steps easy, the uvm_heartbeat has a set of inbuilt methods.

uvm_heartbeat methods

new

function new(
  string name,
  uvm_component cntxt,
  uvm_objection objection = null
)
  • Creates a new heartbeat instance associated with cntxt.
  • The cntxt is the hierarchy of components to be monitored.
  • The object of uvm_objections_callback type

set_heartbeat

function void set_heartbeat (
  uvm_event#(uvm_object) e,
  ref uvm_component comps[$]
)

This method sets a list of objects to be watched at event e.

set_mode

function uvm_heartbeat_modes set_mode (
  uvm_heartbeat_modes mode = UVM_NO_HB_MODE
)

This method call Sets or retrieves the heartbeat mode.

the mode shall be,
UVM_ALL_ACTIVE  – all components
UVM_ONE_ACTIVE – exactly one component
UVM_ANY_ACTIVE – any component

add

function void add (
  uvm_component comp
)

The add method Adds a single component to the set of components to be monitored.

remove

function void remove (
  uvm_component comp
)

The Remove method removes a single component from the set of components being monitored.

start

function void start (
  uvm_event#(uvm_object) e = null
)

Calling the start method Starts the heartbeat monitor.

stop

function void stop ()

Calling stop method Stops the heartbeat monitor.
❮ Previous Next ❯

uvm barrier

uvm_barrier

The uvm barrier class enables synchronization control between the processes.

  • uvm_barrier allows a set of processes to be blocked until the desired number of processes get to the
  • synchronization point
  • Processes get released Once after all the process reaching synchronization point

If the threshold is set to a value less than the number of currently waiting for processes, then the barrier is reset and waiting processes are activated

The uvm_barrier class has below built-in methods,

  • new
  • set_auto_reset
  • set_threshold
  • get_threshold
  • get_num_waiters
  • wait_for
  • reset
  • cancel

uvm_barrier methods

new

function new (
  string name = "",
  int threshold = 0
)

Creates a new barrier object.

wait_for

virtual task wait_for()

This method call, Waits for enough processes to reach the barrier before continuing.

set_threshold

virtual function void set_threshold (
  int threshold
)

The number of processes to wait for is set by the set_threshold method.
This determines how many processes must be waiting on the barrier before the processes may proceed.

get_threshold

virtual function int get_threshold ()

Gets the current threshold setting for the barrier.

get_num_waiters

virtual function int get_num_waiters ()

Returns the number of processes currently waiting at the barrier.

reset

virtual function void reset (
bit wakeup = 1
)

Resets the barrier. This sets the waiter count back to zero.
The threshold is unchanged. After reset, the barrier will force processes to wait for the threshold again. If the wake-up bit is set, any currently waiting processes will be activated.

set_auto_reset

virtual function void set_auto_reset (
  bit value = 1
)

Determines if the barrier should reset itself after the threshold is reached.
The default is on, so when a barrier hits its threshold it will reset, and new processes will block until the threshold is reached again. If auto-reset is off, then once the threshold is achieved, new processes pass through without being blocked until the barrier is reset.

cancel

virtual function void cancel ()

Decrements the waiter count by one. This is used when a process that is waiting on the barrier is killed or activated by some other means.

uvm_barrier usage

Using uvm_barrier involves below methods,

  1. Declare and create the uvm_barrier
  2. Set the process to be waiting
  3. Calling wait_for() method inside the process

refer to next page for uvm_barrier examples
❮ Previous Next ❯

uvm barrier examples

uvm_barrier examples

using new and wait_for methods

The below example shows using the uvm_barrier,

  • uvm_barrier is declared with the name ba
  • the barrier is created by calling ba.new() method, threshold or number of process to wait is an argument for the new method
  • This example has 4 processes with delay in it, and the wati_for method is called after the delay
  • The statements after the wait_for will get executed only after the 3 process reaches to wait_for (3 is threshold have been set during creating the barrier)
module uvm_barrier_ex;
  uvm_barrier ba;
  
  initial begin
    ba = new("ba",3);
    
    fork
      begin       //process-1
        $display($time," Inside the process-a");
        #20;
        $display($time," process-a completed");
        $display($time," process-a Waiting for barrier");
        ba.wait_for();
        $display($time," process-a after wait_for");
      end
      
      begin       //process-2
        $display($time," Inside the process-b");
        #10;
        $display($time," process-b completed");
        $display($time," process-b Waiting for barrier");
        ba.wait_for();
        $display($time," process-b after wait_for");
      end
      
      begin       //process-3
        $display($time," Inside the process-c");
        #30;
        $display($time," process-c completed");
        $display($time," process-c Waiting for barrier");
        ba.wait_for();
        $display($time," process-c after wait_for");
      end
      
      begin       //process-4
        $display($time," Inside the process-d");
        #5;
        $display($time," process-d completed");
        $display($time," process-d Waiting for barrier");
        ba.wait_for();
        $display($time," process-d after wait_for");
      end
    join
  end
endmodule

Simulator Output

0 Inside the process-a
0 Inside the process-b
0 Inside the process-c
0 Inside the process-d
5 process-d completed
5 process-d Waiting for barrier
10 process-b completed
10 process-b Waiting for barrier
20 process-a completed
20 process-a Waiting for barrier
20 process-d after wait_for
20 process-b after wait_for
20 process-a after wait_for
30 process-c completed
30 process-c Waiting for barrier

Click to execute on   

Output Analysis:

The “after wait_for” display is getting executed only after the 3 processes reaching the ba.wait_for(). this is because the threshold is set is 3.

uvm_barrier in function

This example is similar to the above example. In this example, multiple processes are of the same task.

module uvm_barrier_ex;
  uvm_barrier ba;
  
  task automatic process(input string p_name, int delay);
    
    $display($time," [%s] Strating the process",p_name);
    $display($time," [%s] Injecting the delay of %0d",p_name,delay);
    #delay;
    
    $display($time," [%s] Before the wait_for",p_name);
    ba.wait_for();
    $display($time," [%s] After the wait_for",p_name);
  endtask
  
  initial begin
    ba = new("ba",3);
    
    fork
      process("A",30);
      process("B",10);
      process("C",20);
      process("D",5);
    join
  end
endmodule

Simulator Output

0 [A] Strating the process
0 [A] Injecting the delay of 30
0 [B] Strating the process
0 [B] Injecting the delay of 10
0 [C] Strating the process
0 [C] Injecting the delay of 20
0 [D] Strating the process
0 [D] Injecting the delay of 5
5 [D] Before the wait_for
10 [B] Before the wait_for
20 [C] Before the wait_for
20 [D] After the wait_for
20 [B] After the wait_for
20 [C] After the wait_for
30 [A] Before the wait_for

Click to execute on   

using set_threshold method

In the previous examples, we have seen setting the threshold using the new() method. This example shows setting the threshold by using the set_threshold() method.

module uvm_barrier_ex;
  uvm_barrier ba;
  
  task automatic process(input string p_name, int delay);
    
    $display($time," [%s] Strating the process",p_name);
    $display($time," [%s] Injecting the delay of %0d",p_name,delay);
    #delay;
    
    $display($time," [%s] Before the wait_for",p_name);
    ba.wait_for();
    $display($time," [%s] After the wait_for",p_name);
  endtask
  
  initial begin
    ba = new("ba",3);
    ba.set_threshold(3);
    
    fork
      process("A",30);
      process("B",10);
      process("C",20);
      process("D",5);
    join
  end
endmodule

Simulator Output

0 [A] Strating the process
0 [A] Injecting the delay of 30
0 [B] Strating the process
0 [B] Injecting the delay of 10
0 [C] Strating the process
0 [C] Injecting the delay of 20
0 [D] Strating the process
0 [D] Injecting the delay of 5
5 [D] Before the wait_for
10 [B] Before the wait_for
20 [C] Before the wait_for
20 [D] After the wait_for
20 [B] After the wait_for
20 [C] After the wait_for
30 [A] Before the wait_for

Click to execute on   

using get_threshold and get_num_waiters

get_threshold() and get_num_waiters() methods return the threshold value and number of waiters at that instant of time.

module uvm_barrier_ex;
  uvm_barrier ba;
  
  task automatic process(input string p_name, int delay);
    
    $display($time," [%s] Strating the process",p_name);
    $display($time," [%s] Injecting the delay of %0d",p_name,delay);
    #delay;
    
    $display($time," [%s] Before the wait_for",p_name);
    ba.wait_for();
    $display($time," [%s] After the wait_for",p_name);
  endtask
  
  task monitor_process();
    #15;
    $display($time," [Monitor] threshold value of barrie ba
                               is %0d",ba.get_threshold());
    $display($time," [Monitor] Number of process waiting
                               are %0d",ba.get_num_waiters());
  endtask
  
  initial begin
    ba = new("ba");
    
    ba.set_threshold(3);
    
    fork
      process("A",30);
      process("B",10);
      process("C",20);
      process("D",5);
      
      monitor_process();
    join
  end
endmodule

Simulator Output

0 [A] Strating the process
0 [A] Injecting the delay of 30
0 [B] Strating the process
0 [B] Injecting the delay of 10
0 [C] Strating the process
0 [C] Injecting the delay of 20
0 [D] Strating the process
0 [D] Injecting the delay of 5
5 [D] Before the wait_for
10 [B] Before the wait_for
15 [Monitor] threshold value of barrie ba is 3
15 [Monitor] Number of process waiting are 2
20 [C] Before the wait_for
20 [D] After the wait_for
20 [B] After the wait_for
20 [C] After the wait_for
30 [A] Before the wait_for

Click to execute on   

using the reset method

In the previous examples, we can observe that “After the wait_for” of process A is not getting executed. this is because the number of waiters resets after the third process wait_for and the number of process waiting will be one for process A.

Calling reset() method will lead to releasing the process at wait_for() and the statements after the wait_for(0 will get executed.

module uvm_barrier_ex;
  uvm_barrier ba;
  
  task automatic process(input string p_name, int delay);
    
    $display($time," [%s] Strating the process",p_name);
    $display($time," [%s] Injecting the delay of %0d",p_name,delay);
    #delay;
    
    $display($time," [%s] Before the wait_for",p_name);
    ba.wait_for();
    $display($time," [%s] After the wait_for",p_name);
  endtask
  
  task reset_process();
    #32;
    ba.reset(1);
  endtask
  
  initial begin
    ba = new("ba");
    
    ba.set_threshold(3);
    
    fork
      process("A",30);
      process("B",10);
      process("C",20);
      process("D",5);
      reset_process();
    join
  end
endmodule

Simulator Output

0 [A] Strating the process
0 [A] Injecting the delay of 30
0 [B] Strating the process
0 [B] Injecting the delay of 10
0 [C] Strating the process
0 [C] Injecting the delay of 20
0 [D] Strating the process
0 [D] Injecting the delay of 5
5 [D] Before the wait_for
10 [B] Before the wait_for
20 [C] Before the wait_for
20 [D] After the wait_for
20 [B] After the wait_for
20 [C] After the wait_for
30 [A] Before the wait_for
32 [A] After the wait_for

Click to execute on   

using the set_auto_reset method

This is similar to the above example, in this example auto_reset is set during the initial time. with auto_reset, once the threshold is achieved, new processes pass through without being blocked until the barrier is reset.

module uvm_barrier_ex;
  uvm_barrier ba;
  
  task automatic process(input string p_name, int delay);
    
    $display($time," [%s] Strating the process",p_name);
    $display($time," [%s] Injecting the delay of %0d",p_name,delay);
    #delay;
    
    $display($time," [%s] Before the wait_for",p_name);
    ba.wait_for();
    $display($time," [%s] After the wait_for",p_name);
  endtask
  
  initial begin
    ba = new("ba");
    
    ba.set_threshold(3);
    ba.set_auto_reset(0);
    
    fork
      process("A",30);
      process("B",10);
      process("C",20);
      process("D",5);
    join
  end
endmodule

Simulator Output

0 [A] Strating the process
0 [A] Injecting the delay of 30
0 [B] Strating the process
0 [B] Injecting the delay of 10
0 [C] Strating the process
0 [C] Injecting the delay of 20
0 [D] Strating the process
0 [D] Injecting the delay of 5
5 [D] Before the wait_for
10 [B] Before the wait_for
20 [C] Before the wait_for
20 [D] After the wait_for
20 [B] After the wait_for
20 [C] After the wait_for
30 [A] Before the wait_for
30 [A] After the wait_for

Click to execute on   

Output Analysis:

In process A, though the process wait_for count is ‘1’. the wait_for will becomes non_blocking and “After the wait_for” statement gets executed.

Using the cancel method

Calling cancel() method will decrement the number of process waiting.

module uvm_barrier_ex;
  uvm_barrier ba;
  
  task automatic process(input string p_name, int delay);
    
    $display($time," [%s] Strating the process",p_name);
    $display($time," [%s] Injecting the delay of %0d",p_name,delay);
    #delay;
    
    $display($time," [%s] Before the wait_for",p_name);
    ba.wait_for();
    $display($time," [%s] After the wait_for",p_name);
  endtask
  task cancel_process();
    #20;
    $display($time," Number of process waiting before cancel
                     is %0d",ba.get_num_waiters());
    ba.cancel();
    $display($time," Number of process waiting after cancel
                     is %0d",ba.get_num_waiters());
  endtask
  
  initial begin
    ba = new("ba");
    
    ba.set_threshold(2);
    
    fork
      process("A",30);
      process("B",10);
      cancel_process();
    join
  end
endmodule

Simulator Output

0 [A] Starting the process
0 [A] Injecting the delay of 30
0 [B] Starting the process
0 [B] Injecting the delay of 10
10 [B] Before the wait_for
20 Number of process waiting before cancel is 1
20 Number of process waiting after cancel is 0
30 [A] Before the wait_for

Click to execute on   

Output Analysis:

  • The threshold is set as ‘2’
  • cancel method will get executed after the process B wait_for execution. So the number of processes waiting before the cancel is 1 and after the cancel becomes 0.
  • On execution of wait_for of process A, the count becomes 1, and it remains 1 as there is no other process, which leads to the wait_for method to keep blocked
  • Statement after wait_for will not get executed

❮ Previous Next ❯

uvm_event wait trigger data

uvm_event with parameter data type

The uvm_event defined with the optional parameter T allows the user to define a data type that can be passed during an event trigger.

This is one of the key benefits of uvm_event. Along with the event triggering, data can be passed, this data can be retrieved at wait for the event trigger.

Syntax to declare

uvm_event#(T)
// T - user defined data type

uvm_event class declaration in uvm library,

class uvm_event#(
type T = uvm_object
) extends uvm_event_base

Note:
T is of  uvm_object type, passing the type other than uvm_object leads to a compilation error.

Methods

  • new
    • Creates a new event object
  • trigger
    • Triggers the event, resuming all waiting processes.
  • get_trigger_data
    • Gets the data, if any, provided by the last call to trigger
  • wait_trigger_data
    • This method calls uvm_event_base::wait_trigger followed by get_trigger_data
  • wait_ptrigger_data
    • This method calls uvm_event_base::wait_ptrigger followed by get_trigger_data

uvm_event with parameter example

The below example consists of two components comp_a and comp_b. comp_a will trigger an event and comp_b wait for the event trigger. Along with the event trigger, comp_a will randomize the transaction object trans and send it to the comp_b via event trigger.

Below are the methods used in this example,

  • ev.trigger(trans) – triggers an event and send the trans.
  • ev.wait_trigger   – wait for an event trigger.
  • ev.get_trigger_data() – retrive the data from event trigger.
  • $cast(trans,ev.get_trigger_data()) – return type of get_trigger_data is object, $cast is used for assignment.

comp_a code

  //---------------------------------------
  // run_phase 
  //---------------------------------------
  virtual task run_phase(uvm_phase phase);
    phase.raise_objection(this);
    
    #10;
    trans = transaction::type_id::create("trans", this);
    
    trans.randomize();
    `uvm_info(get_type_name(),$sformatf(" randomized trans, \n %s",
                                          trans.sprint()),UVM_LOW)
    
    ev = uvm_event_pool::get_global("ev_ab"); //Step-1. get event from global pool
    
    `uvm_info(get_type_name(),$sformatf(" Before triggering the event"),UVM_LOW)
    
    ev.trigger(trans); //Step-2. trigger an event and send trans
    
    `uvm_info(get_type_name(),$sformatf(" After triggering the event"),UVM_LOW)

    phase.drop_objection(this);
  endtask : run_phase

comp_b code

  //---------------------------------------
  // run_phase 
  //---------------------------------------
  virtual task run_phase(uvm_phase phase);
    phase.raise_objection(this); 
    
    ev = uvm_event_pool::get_global("ev_ab"); //Step-1. get event from global pool
    
    `uvm_info(get_type_name(),$sformatf(" waiting for the event trigger"),UVM_LOW)
    
    ev.wait_trigger; //Step-2. wait for an event trigger
    
    `uvm_info(get_type_name(),$sformatf(" event got triggerd"),UVM_LOW)
    
    $cast(trans,ev.get_trigger_data()); //Step-3. retrive the data from event trigger
    `uvm_info(get_type_name(),$sformatf(" trans received, \n %s",trans.sprint()),UVM_LOW)

    phase.drop_objection(this);
  endtask : run_phase

Simulator Output

UVM_INFO @ 0: reporter [RNTST] Running test basic_test...
--------------------------------------
Name Type Size Value
--------------------------------------
uvm_test_top basic_test - @335 
 comp_a component_a - @348 
 comp_b component_b - @357 
--------------------------------------
UVM_INFO component_b.sv(27) @ 0: uvm_test_top.comp_b [component_b] waiting for the event trigger
UVM_INFO component_a.sv(29) @ 10: uvm_test_top.comp_a [component_a] randomized trans, 
 ---------------------------------
Name Type Size Value
---------------------------------
trans transaction - @392 
 addr integral 4 'hb 
 wr_rd integral 1 'h1 
 wdata integral 8 'h54 
---------------------------------

UVM_INFO component_a.sv(33) @ 10: uvm_test_top.comp_a [component_a] Before triggering the event
UVM_INFO component_a.sv(37) @ 10: uvm_test_top.comp_a [component_a] After triggering the event
UVM_INFO component_b.sv(31) @ 10: uvm_test_top.comp_b [component_b] event got triggerd
UVM_INFO component_b.sv(34) @ 10: uvm_test_top.comp_b [component_b] trans received, 
 ---------------------------------
Name Type Size Value
---------------------------------
trans transaction - @392 
 addr integral 4 'hb 
 wr_rd integral 1 'h1 
 wdata integral 8 'h54 
---------------------------------

UVM_INFO /apps/vcsmx/etc/uvm-1.2/src/base/uvm_objection.svh(1270) @ 10: reporter [TEST_DONE]
UVM_INFO /apps/vcsmx/etc/uvm-1.2/src/base/uvm_report_server.svh(847) @ 10: reporter [UVM/REPORT/SERVER]

Click to execute on   

uvm_event with parameter type int

In the below example, the int type is passed along with the event trigger. As seen above passing a data type other than uvm_object leads to a compilation error.

  //---------------------------------------
  // run_phase 
  //---------------------------------------
  virtual task run_phase(uvm_phase phase);
    phase.raise_objection(this);
    
    ev  = uvm_event_pool::get_global("ev_ab");
    
    `uvm_info(get_type_name(),$sformatf(" Before triggering the event"),UVM_LOW)
    
    ev.trigger(30);
    
    `uvm_info(get_type_name(),$sformatf(" After triggering the event"),UVM_LOW)

    phase.drop_objection(this);
  endtask : run_phase

Simulator Output

ncelab: *W,DSEMEL: This SystemVerilog design will be simulated as per IEEE 1800-2009 SystemVerilog 
simulation semantics. Use -disable_sem2009 option for turning off SV 2009 simulation semantics.
 ev.trigger(30);
 |
ncelab: *E,TYCMPAT (./component_a.sv,28|16): formal and actual do not have assignment compatible 
data types (expecting datatype compatible with 'class uvm_pkg::uvm_object' but found 'integer' instead).
irun: *E,ELBERR: Error during elaboration (status 1), exiting.
Exit code expected: 0, received: 1

Click to execute on   

❮ Previous Next ❯

uvm_event wait ptrigger

uvm event wait_ptrigger

wait ptrigger example

event trigger and wait for event trigger at the same time

As seen in the previous example, triggering the event and waiting for event trigger with wait_trigger leads to race conditions and the event trigger will not get detected.

UVM_Event provides wait_ptrigger to overcome race conditions.

Below is the example with wait_ptrigger.

module uvm_events_ex;
  uvm_event ev_1; //declaring uvm_event ev_1
  
  initial begin
    ev_1 = new(); //Creating the event
    
    fork
      //process-1, triggers the event
      begin
        $display($time," Triggering The Event");
        ev_1.trigger;
      end
      
      //process-2, wait for the event to trigger
      begin
        $display($time," Waiting for the Event to trigger");
        ev_1.wait_ptrigger;
        $display($time," Event triggered");
      end
    join
  end
  initial begin
    #100;
    $display($time," Ending the Simulation");
    $finish;
  end
endmodule

Simulator Output

0 Triggering The Event
0 Waiting for the Event to trigger
0 Event triggered
100 Ending the Simulation

Click to execute on   

❮ Previous Next ❯

UVM Objection / Managing End of Test

 UVM Objection 

UVM provides an objection mechanism to allow hierarchical status communication among components which is helpful in deciding the end of test.
There is a built-in objection for each phase, which provides a way for components and objects to synchronize their testing activity and indicate when it is safe to end the phase and, ultimately, the test end.
The component or sequence will raise a phase objection at the beginning of an activity that must be completed before the phase stops, so the objection will be dropped at the end of that activity. Once all of the raised objections are dropped, the phase terminates.
Lets consider an example,
A master agent may need to complete all its write and read operations before the run phase should be allowed to stop.
In this example, typically objections can either be added in sequence or test.

Objection in sequence,
objection is raised when it started as a root sequence (a sequence which has no parent sequence), and to drop the objection when it is finished as a root sequence.

class wr_rd_seq extends uvm_sequence#(mem_seq_item);
  task pre_body();
    // raise objection if started as a root sequence
    if(starting_phase != null)
      starting_phase.raise_objection(this);
  endtask

  task body();
    `uvm_do_with(req,wr_en==1);
    `uvm_do_with(req,rd_en==1);
  endtask

  task post_body();
    // drop objection if started as a root sequence
    if(starting_phase != null)
      starting_phase.drop_objection(this);
  endtask
endclass
The starting_phase member is only set automatically if the sequence is started as the default sequence
for a particular phase. so objections were used in the sequence if it is default sequence for a particular phase.

Objection in test,
If the sequence need to be started explicitly i.e in the test, then the objections were added in the test instead of sequence

class wr_rd_test extends uvm_test;
  
  -----
  -----
  
  task main_phase(uvm_phase phase);
    phase.raise_objection(); //rasing objection
      wr_rd_seq.start(mem_agent.sequencer);
    phase.drop_objection();  //droping objection
  endtask
    
endclass

Note:
When the sequence is started explicitly, the starting_phase member is null, so the sequence will not raise or drop the phase objection.

❮ Previous Next ❯

m_sequencer and p_sequencer

difference between m_sequencer and p_sequencer

m_sequencer,

The m_sequencer handle contains the reference to the sequencer(default sequencer) on which the sequence is running.

This is determined by,

  • the sequencer handle provided in the start method
  • the sequencer used by the parent sequence
  • the sequencer that was set using the set_sequencer method

p_sequencer,

The p_sequencer is a variable, used as handle to access the sequencer properties.
p_sequencer is defined using the macro `uvm_declare_p_sequencer(SEQUENCER_NAME)

UVM Environment Example

UVM Environment

User-defined environment is derived from uvm_env, uvm_env is inherited from uvm_component.

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

UVM Environment
UVM Environment

Writing UVM Environment

1. Environment is written by extending 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, 

  mem_agent mem_agnt;

3. Create agent, 

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

UVM environment code,

class mem_model_env extends uvm_env;

  mem_agent mem_agnt;
  
  `uvm_component_utils(mem_model_env)
    
  // new - 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);
    mem_agnt = mem_agent::type_id::create("mem_agnt", this);
  endfunction : build_phase

endclass : mem_model_env