What is a UVM agent ?

An agent encapsulates a Sequencer, Driver and Monitor into a single entity by instantiating and connecting the components together via TLM interfaces. Since UVM is all about configurability, an agent can also have configuration options like the type of UVM agent (active/passive), knobs to turn on features such as functional coverage, and other similar parameters.

What are all the types of agents ?

Active
  • Instantiates all three components [Sequencer, Driver, Monitor]
  • Enables data to be driven to DUT via driver
Passive
  • Only instantiate the monitor
  • Used for checking and coverage only
  • Useful when there's no data item to be driven to DUT

How to find out if a UVM agent is active or passive ?

User-defined agent classes derived from uvm_agent also have another function called get_is_active() which will return the state of the requested UVM agent.

  
  
	// Assume this is inside the user-defined agent class
	if (get_is_active()) begin
		// Build driver and sequencer
	end
	// Build monitor

  

Steps to create a UVM agent

1. Create a custom class inherited from uvm_agent, register with factory and callnew
  
  
// my_agent is user-given name for this class that has been derived from "uvm_agent"
class my_agent extends uvm_agent;
 
    // [Recommended] Makes this agent more re-usable
    `uvm_component_utils (my_agent)
 
    // This is standard code for all components
    function new (string name = "my_agent", uvm_component parent = null);
      super.new (name, parent);
    endfunction
 
    // Code for rest of the steps come here
endclass

  
2. Instantiate agent components
  
  
	// Create handles to all agent components like driver, monitor and sequencer
	// my_driver, my_monitor and agent_cfg are custom classes assumed to be defined
	// Agents can be configured via a configuration object that can be passed in from the test
	my_driver                  m_drv0;
    my_monitor                 m_mon0;
    uvm_sequencer #(my_data)   m_seqr0;
    agent_cfg                  m_agt_cfg;

  
3. Instantiate and build components
  
  
      virtual function void build_phase (uvm_phase phase);
		 
		 // If this UVM agent is active, then build driver, and sequencer
         if (get_is_active()) begin
            m_seqr0 = uvm_sequencer#(my_data)::type_id::create ("m_seqr0", this);
            m_drv0 = my_driver::type_id::create ("m_drv0", this);
         end
         
         // Both active and passive agents need a monitor
         m_mon0 = my_monitor::type_id::create ("m_mon0", this);
         
         //[Optional] Get any agent configuration objects from uvm_config_db
      endfunction

  
4. Connect agent components together
  
  
      virtual function void connect_phase (uvm_phase phase);
      
		 // Connect the driver to the sequencer if this agent is Active
         if (get_is_active())
            m_drv0.seq_item_port.connect (m_seqr0.seq_item_export);
      endfunction

  

What does a UVM agent do ?

Usually, it makes sense to create an agent that provides protocol specific tasks to generate transactions, check the results and perform coverage. For example, a UVM agent can be created for the WishBone protocol whose sequencer will generate data items which can be sent to the driver. The driver then converts the data item class object into actual pin level signals and drive them to the DUT. The monitor may passively collect the outputs from the DUT, convert them back into another data item class object and distribute it among all the components in the testbench waiting for the item.

There is a third type of fork join in SystemVerilog which is fork and join_none.

A fork and join_none will allow the main thread to resume execution of further statements that lie after the fork regardless of whether the forked threads finish. If five threads are launched, the main thread will resume execution immediately while all the five threads remain running in the background.

Syntax

  
  
	fork
		// Thread 1
		// Thread 2
		// ...
		// Thread N
	join_none

  

fork join_none Example

  
  
module tb;
	initial begin
      $display ("[%0t] Main Thread: Fork join going to start", $time);
		fork
          print (20, "Thread1_0");
          print (30, "Thread1_1");
          print (10, "Thread2");              
		join_none
      $display ("[%0t] Main Thread: Fork join has finished", $time);
	end
  
  // Note that we need automatic task
  task automatic print (int _time, string t_name);
    #(_time) $display ("[%0t] %s", $time, t_name);
  endtask
endmodule

  
Simulation Log
ncsim> run
[0] Main Thread: Fork join going to start
[0] Main Thread: Fork join has finished
[10] Thread2
[20] Thread1_0
[30] Thread1_1
ncsim: *W,RNQUIE: Simulation is complete.

Nested fork join_none

  
  
module tb;
	initial begin
      $display ("[%0t] Main Thread: Fork join going to start", $time);
		fork
          begin
			fork
              print (20, "Thread1_0");
              print (30, "Thread1_1");
            join_none
            $display("[%0t] Nested fork has finished", $time);
          end
          print (10, "Thread2");              
        join_none
      $display ("[%0t] Main Thread: Fork join has finished", $time);
	end
  
  // Note that we need automatic task
  task automatic print (int _time, string t_name);
    #(_time) $display ("[%0t] %s", $time, t_name);
  endtask
endmodule

  
Simulation Log
ncsim> run
[0] Main Thread: Fork join going to start
[0] Main Thread: Fork join has finished
[0] Nested fork has finished
[10] Thread2
[20] Thread1_0
[30] Thread1_1
ncsim: *W,RNQUIE: Simulation is complete.

Why do we need automatic task ?

Without automatic keyword, the same display task with different string tags will produce the same display message. This is because multiple threads call the same task and share the same variable in tool simulation memory. In order for different threads to initiate different copies of the same task, automatic keyword has to be used.

  
  
module tb;
  initial begin
      $display ("[%0t] Main Thread: Fork join going to start", $time);
    fork
          print (20, "Thread1_0");
          print (30, "Thread1_1");
          print (10, "Thread2");              
    join_none
      $display ("[%0t] Main Thread: Fork join has finished", $time);
  end
 
  // Note that this is not an automatic task, its static
  task print (int _time, string t_name);
    #(_time) $display ("[%0t] %s", $time, t_name);
  endtask
endmodule

  
Simulation Log
ncsim> run
[0] Main Thread: Fork join going to start
[0] Main Thread: Fork join has finished
[10] Thread2
[20] Thread2
[30] Thread2
ncsim: *W,RNQUIE: Simulation is complete.

TLM 2.0 introduced socket which enables asynchronous bi-directional data transfer between the initiator and target component. A socket is derived from the same base class as ports and export - uvm_port_base. Components that initiate transactions have initiator sockets and are called initiators, while components that receive transactions have target sockets and are called targets. Note that initiator sockets can be connected only to target sockets and target sockets only to initiator sockets.

The put/get communication we have seen earlier typically require a corresponding export to supply the implementation. The idea behind having an analysis port is that a component like monitor should be able to generate a stream of transactions regardless of whether there is a target actually connected to it.

The uvm_analysis_port is a specialized TLM based class whose interface consists of a single function write () and can be embedded within any component as shown in the snippet below. This port contains a list of analysis exports that are connected to it. When the component (my_monitor) calls analysis_port.write(), it basically cycles through the list and calls the write() method of each connected export. If nothing is connected to it, then it simply does not do anything.

  
  
class my_monitor extends uvm_component;
	...
	uvm_analysis_port #(my_data) analysis_port;
	...
endclass

  

The advantage lies in the fact that an analysis port may be connected to zero, one or many analysis exports and allows a component to call write() method without depending on the number of connected exports. Also it's worth to note that write() is a void function and hence will always complete within the same simulation delta cycle.

Example

Let's take component B from the TLM Put example and connect three Subscribers to it. In order for component B to broadcast transactions to it's subscribers, there needs to be a special channel for communication. This is implemented by analysis ports. Analysis ports are declared using uvm_analysis_port so that component B can send transactions into the channel. We'll see how we can connect subscribers to this channel in a short while.

This UVM TLM example uses put ports, TLM FIFOs and get ports discussed in previous articles to build a testbench that has TLM ports at different levels.

tlm-hier

TLM FIFO can be extended to have another component called componentB to accept packets using another internal FIFO and sub-component.

A class called Packet is defined below to act as the data item that will be transferred from one component to another. This class object will have two random variables that can be randomized before sending.

  
  
class Packet extends uvm_object;
  rand bit[7:0] addr;
  rand bit[7:0] data;
 
  `uvm_object_utils_begin(Packet)
    `uvm_field_int(addr, UVM_ALL_ON)
    `uvm_field_int(data, UVM_ALL_ON)
  `uvm_object_utils_end
 
  function new(string name = "Packet");
    super.new(name);
  endfunction
endclass	

  

subComp1

This component is exactly the same as in UVM TLM FIFO example and starts sending packets using its uvm_blocking_put_port.

  
  
class subComp1 extends uvm_component;
   `uvm_component_utils (subComp1)
 
  // Create a blocking TLM put port which can send an object
  // of type 'Packet'
  uvm_blocking_put_port #(Packet) m_put_port;
  int m_num_tx;
 
   function new (string name = "subComp1", uvm_component parent= null);
      super.new (name, parent);
   endfunction
 
   // Remember that TLM put_port is a class object and it will have to be 
   // created with new ()
   virtual function void build_phase (uvm_phase phase);
      super.build_phase (phase);
     m_put_port = new ("m_put_port", this);
   endfunction
 
  // Create a packet, randomize it and send it through the port
  // Note that put() is a method defined by the receiving component
  // Repeat these steps N times to send N packets
   virtual task run_phase (uvm_phase phase);
     repeat (m_num_tx) begin
         Packet pkt = Packet::type_id::create ("pkt");
         assert(pkt.randomize ()); 
 		#50;
          // Print the packet to be displayed in log
       `uvm_info ("SUBCOMP1", "Packet sent to compA:tlm_fifo", UVM_LOW)
         pkt.print (uvm_default_line_printer);
 
         // Call the TLM put() method of put_port class and pass packet as argument
         m_put_port.put (pkt);
      end
   endtask   
endclass

  

subComp2

This sub-component is slightly slower in receiving packets compared to the one seen above, and accepts data through uvm_blocking_get_port.

Note that this also has another put port of type uvm_blocking_put_port to forward data out of componentA.

  
  
class subComp2 extends uvm_component;
   `uvm_component_utils (subComp2)
 
   // Create a get_port to request for data from subComp1
   uvm_blocking_get_port #(Packet) m_get_port;
  uvm_blocking_put_port #(Packet) m_put_port;
 
  function new (string name, uvm_component parent);
      super.new (name, parent);
   endfunction
 
   virtual function void build_phase (uvm_phase phase);
      super.build_phase (phase);
      m_get_port = new ("m_get_port", this);
     m_put_port = new ("m_put_port", this);
   endfunction
 
   virtual task run_phase (uvm_phase phase);
      Packet pkt;
      forever begin
       #100;
         m_get_port.get (pkt);
        `uvm_info ("SUBCOMP2", "Packet received from compA:tlm_fifo, forward it", UVM_LOW)
        pkt.print (uvm_default_line_printer);
        m_put_port.put(pkt);
      end
   endtask
endclass   

  

ComponentA

This layer contains both sub-components connected together by a TLM FIFO since their transfer rates are different.

Note that it also has another put port of type uvm_blocking_put_port to forward the packet it receives from subComp2 which is at a lower layer.

  
  
class componentA extends uvm_component;
   `uvm_component_utils (componentA)
  function new(string name="componentA", uvm_component parent=null);
    super.new(name, parent);
  endfunction
  
   subComp1 m_subcomp_1;
   subComp2 m_subcomp_2;
 
   uvm_tlm_fifo #(Packet)    		m_tlm_fifo;
   uvm_blocking_put_port #(Packet)  m_put_port;
   int 								m_num_tx;
 
   virtual function void build_phase (uvm_phase phase);
      super.build_phase (phase);
      // Create an object of both components
      m_subcomp_1 = subComp1::type_id::create ("m_subcomp_1", this);
      m_subcomp_2 = subComp2::type_id::create ("m_subcomp_2", this);
 
      // Create a FIFO with depth 2
      m_tlm_fifo = new ("uvm_tlm_fifo", this, 2);
      m_put_port = new ("m_put_port", this);
      m_subcomp_1.m_num_tx = m_num_tx;
   endfunction
 
   // Make componentA connections
   virtual function void connect_phase (uvm_phase phase);
     // Connect put port from subComp1 to TLM FIFO and then 
     // connect get_export of TLM FIFO with subComp2
     m_subcomp_1.m_put_port.connect(m_tlm_fifo.put_export);
     m_subcomp_2.m_get_port.connect(m_tlm_fifo.get_export);
     
     // Now connect subComp2 to componentA for forwarding pkt
     m_subcomp_2.m_put_port.connect(this.m_put_port);
   endfunction
 
   // Display a message when the FIFO is full
   virtual task run_phase (uvm_phase phase);
      forever begin
        #10 if (m_tlm_fifo.is_full ()) 
          `uvm_info ("COMPA", "componentA:TLM_Fifo is now FULL !", UVM_MEDIUM)
      end
   endtask
endclass

  

subComp3

Assume the destination component is even slower than the other components seen above and accepts packet using a uvm_blocking_get_port.

  
  
// subComp3 accepts packet even slower than what componentA is sending out          
// which is the reason we need a TLM FIFO in componentB
class subComp3 extends uvm_component;
  `uvm_component_utils (subComp3)
  
   // Create a get_port to request for data from subComp1
   uvm_blocking_get_port #(Packet) m_get_port;
   int m_num_tx;
 
  function new (string name, uvm_component parent);
      super.new (name, parent);
   endfunction
 
   virtual function void build_phase (uvm_phase phase);
      super.build_phase (phase);
      m_get_port = new ("m_get_port", this);
   endfunction
 
   virtual task run_phase (uvm_phase phase);
      Packet pkt;
     repeat(m_num_tx) begin
        #200;
        m_get_port.get (pkt);
        `uvm_info ("SUBCOMP3", "Packet received from componentA", UVM_LOW)
        pkt.print (uvm_default_line_printer);
      end
   endtask
endclass

  

ComponentB

Another UVM TLM FIFO is required to be connected to subComp3 to buffer packets it receives because of the slower rate of destination. Note that this has a top level put_export port of type uvm_blocking_put_export.

  
  
class componentB extends uvm_component;
   `uvm_component_utils (componentB)
 
   subComp3                    			m_subcomp_3;
  uvm_tlm_fifo #(Packet)    			m_tlm_fifo;
   uvm_blocking_put_export #(Packet) 	m_put_export;
   int 									m_num_tx;
 
   function new (string name = "componentB", uvm_component parent = null);
      super.new (name, parent);
   endfunction
 
   virtual function void build_phase (uvm_phase phase);
      super.build_phase (phase);
      // Create an object of both components
     m_subcomp_3 = subComp3::type_id::create ("m_subcomp_3", this);
 
      // Create a FIFO with depth 2
      m_tlm_fifo = new ("tlm_fifo", this, 2);
 
      // Create the export to connect with componentA
     m_put_export = new ("m_put_export", this);
     
     m_subcomp_3.m_num_tx = m_num_tx;
   endfunction
 
   virtual function void connect_phase (uvm_phase phase);
      // Connect from componentB export to FIFO export
     m_put_export.connect (m_tlm_fifo.put_export);
 
      // Connect from FIFO export to subComponent3 port 
     m_subcomp_3.m_get_port.connect (m_tlm_fifo.get_export);
   endfunction
  
     // Display a message when the FIFO is full
   virtual task run_phase (uvm_phase phase);
      forever begin
        #10 if (m_tlm_fifo.is_full ()) 
          `uvm_info ("COMPB", "componentB:TLM_Fifo is now FULL !", UVM_MEDIUM)
      end
   endtask
endclass

  

Top Env/Test

The connection between a port and its implementation has to be done at a higher hierarchical level. Since both components are instantiated directly within the test class in this example, the connection between them can be done during the connect_phase of the test. If these two components were instantiated in another component or environment, they have to be connected during the connect_phase of that component or environment.

  
  
class my_test extends uvm_env;
  `uvm_component_utils (my_test)
 
  componentA compA;
  componentB compB;
  int 		 m_num_tx;
  
  function new (string name = "my_test", uvm_component parent = null);
      super.new (name, parent);
   endfunction
 
   virtual function void build_phase (uvm_phase phase);
      super.build_phase (phase);
     compA = componentA::type_id::create("componentA", this);
     compB = componentB::type_id::create("componentB", this);
     
     std::randomize(m_num_tx) with { m_num_tx inside {[4:10]}; };
     `uvm_info("TEST", $sformatf("Create %0d packets in total", m_num_tx), UVM_LOW)
     compA.m_num_tx = m_num_tx;
     compB.m_num_tx = m_num_tx;
   endfunction
 
   // Connect the ports to the export of FIFO.
   virtual function void connect_phase (uvm_phase phase);
     compA.m_put_port.connect(compB.m_put_export);
   endfunction
  
  virtual task run_phase(uvm_phase phase);
    super.run_phase(phase);
    // Let all components finish for purpose of illustration
    phase.raise_objection(this);
    #1000;
    phase.drop_objection(this);
  endtask
endclass

  
Simulation Log
UVM_INFO @ 0: reporter [RNTST] Running test my_test...
UVM_INFO testbench.sv(220) @ 0: uvm_test_top [TEST] Create 4 packets in total
UVM_INFO testbench.sv(48) @ 50: uvm_test_top.componentA.m_subcomp_1 [SUBCOMP1] Packet sent to compA:tlm_fifo
pkt: ([email protected]) { addr: 'h1f  data: 'h31  } 
UVM_INFO testbench.sv(79) @ 100: uvm_test_top.componentA.m_subcomp_2 [SUBCOMP2] Packet received from compA:tlm_fifo, forward it
pkt: ([email protected]) { addr: 'h1f  data: 'h31  } 
UVM_INFO testbench.sv(48) @ 100: uvm_test_top.componentA.m_subcomp_1 [SUBCOMP1] Packet sent to compA:tlm_fifo
pkt: ([email protected]) { addr: 'hf4  data: 'h91  } 
UVM_INFO testbench.sv(48) @ 150: uvm_test_top.componentA.m_subcomp_1 [SUBCOMP1] Packet sent to compA:tlm_fifo
pkt: ([email protected]) { addr: 'hab  data: 'ha8  } 
UVM_INFO testbench.sv(126) @ 150: uvm_test_top.componentA [COMPA] componentA:TLM_Fifo is now FULL !
UVM_INFO testbench.sv(126) @ 160: uvm_test_top.componentA [COMPA] componentA:TLM_Fifo is now FULL !
UVM_INFO testbench.sv(126) @ 170: uvm_test_top.componentA [COMPA] componentA:TLM_Fifo is now FULL !
UVM_INFO testbench.sv(126) @ 180: uvm_test_top.componentA [COMPA] componentA:TLM_Fifo is now FULL !
UVM_INFO testbench.sv(126) @ 190: uvm_test_top.componentA [COMPA] componentA:TLM_Fifo is now FULL !
UVM_INFO testbench.sv(154) @ 200: uvm_test_top.componentB.m_subcomp_3 [SUBCOMP3] Packet received from componentA
pkt: ([email protected]) { addr: 'h1f  data: 'h31  } 
UVM_INFO testbench.sv(79) @ 200: uvm_test_top.componentA.m_subcomp_2 [SUBCOMP2] Packet received from compA:tlm_fifo, forward it
pkt: ([email protected]) { addr: 'hf4  data: 'h91  } 
UVM_INFO testbench.sv(48) @ 200: uvm_test_top.componentA.m_subcomp_1 [SUBCOMP1] Packet sent to compA:tlm_fifo
pkt: ([email protected]) { addr: 'h77  data: 'he7  } 
UVM_INFO testbench.sv(126) @ 200: uvm_test_top.componentA [COMPA] componentA:TLM_Fifo is now FULL !
UVM_INFO testbench.sv(126) @ 210: uvm_test_top.componentA [COMPA] componentA:TLM_Fifo is now FULL !
UVM_INFO testbench.sv(126) @ 220: uvm_test_top.componentA [COMPA] componentA:TLM_Fifo is now FULL !
UVM_INFO testbench.sv(126) @ 230: uvm_test_top.componentA [COMPA] componentA:TLM_Fifo is now FULL !
UVM_INFO testbench.sv(126) @ 240: uvm_test_top.componentA [COMPA] componentA:TLM_Fifo is now FULL !
UVM_INFO testbench.sv(126) @ 250: uvm_test_top.componentA [COMPA] componentA:TLM_Fifo is now FULL !
UVM_INFO testbench.sv(126) @ 260: uvm_test_top.componentA [COMPA] componentA:TLM_Fifo is now FULL !
UVM_INFO testbench.sv(126) @ 270: uvm_test_top.componentA [COMPA] componentA:TLM_Fifo is now FULL !
UVM_INFO testbench.sv(126) @ 280: uvm_test_top.componentA [COMPA] componentA:TLM_Fifo is now FULL !
UVM_INFO testbench.sv(126) @ 290: uvm_test_top.componentA [COMPA] componentA:TLM_Fifo is now FULL !
UVM_INFO testbench.sv(79) @ 300: uvm_test_top.componentA.m_subcomp_2 [SUBCOMP2] Packet received from compA:tlm_fifo, forward it
pkt: ([email protected]) { addr: 'hab  data: 'ha8  } 
UVM_INFO testbench.sv(198) @ 300: uvm_test_top.componentB [COMPB] componentB:TLM_Fifo is now FULL !
UVM_INFO testbench.sv(198) @ 310: uvm_test_top.componentB [COMPB] componentB:TLM_Fifo is now FULL !
UVM_INFO testbench.sv(198) @ 320: uvm_test_top.componentB [COMPB] componentB:TLM_Fifo is now FULL !
UVM_INFO testbench.sv(198) @ 330: uvm_test_top.componentB [COMPB] componentB:TLM_Fifo is now FULL !
UVM_INFO testbench.sv(198) @ 340: uvm_test_top.componentB [COMPB] componentB:TLM_Fifo is now FULL !
UVM_INFO testbench.sv(198) @ 350: uvm_test_top.componentB [COMPB] componentB:TLM_Fifo is now FULL !
UVM_INFO testbench.sv(198) @ 360: uvm_test_top.componentB [COMPB] componentB:TLM_Fifo is now FULL !
UVM_INFO testbench.sv(198) @ 370: uvm_test_top.componentB [COMPB] componentB:TLM_Fifo is now FULL !
UVM_INFO testbench.sv(198) @ 380: uvm_test_top.componentB [COMPB] componentB:TLM_Fifo is now FULL !
UVM_INFO testbench.sv(198) @ 390: uvm_test_top.componentB [COMPB] componentB:TLM_Fifo is now FULL !
UVM_INFO testbench.sv(154) @ 400: uvm_test_top.componentB.m_subcomp_3 [SUBCOMP3] Packet received from componentA
pkt: ([email protected]) { addr: 'hf4  data: 'h91  } 
UVM_INFO testbench.sv(79) @ 400: uvm_test_top.componentA.m_subcomp_2 [SUBCOMP2] Packet received from compA:tlm_fifo, forward it
pkt: ([email protected]) { addr: 'h77  data: 'he7  } 
UVM_INFO testbench.sv(198) @ 400: uvm_test_top.componentB [COMPB] componentB:TLM_Fifo is now FULL !
UVM_INFO testbench.sv(198) @ 410: uvm_test_top.componentB [COMPB] componentB:TLM_Fifo is now FULL !
UVM_INFO testbench.sv(198) @ 420: uvm_test_top.componentB [COMPB] componentB:TLM_Fifo is now FULL !
UVM_INFO testbench.sv(198) @ 430: uvm_test_top.componentB [COMPB] componentB:TLM_Fifo is now FULL !
UVM_INFO testbench.sv(198) @ 440: uvm_test_top.componentB [COMPB] componentB:TLM_Fifo is now FULL !
UVM_INFO testbench.sv(198) @ 450: uvm_test_top.componentB [COMPB] componentB:TLM_Fifo is now FULL !
UVM_INFO testbench.sv(198) @ 460: uvm_test_top.componentB [COMPB] componentB:TLM_Fifo is now FULL !
UVM_INFO testbench.sv(198) @ 470: uvm_test_top.componentB [COMPB] componentB:TLM_Fifo is now FULL !
UVM_INFO testbench.sv(198) @ 480: uvm_test_top.componentB [COMPB] componentB:TLM_Fifo is now FULL !
UVM_INFO testbench.sv(198) @ 490: uvm_test_top.componentB [COMPB] componentB:TLM_Fifo is now FULL !
UVM_INFO testbench.sv(198) @ 500: uvm_test_top.componentB [COMPB] componentB:TLM_Fifo is now FULL !
UVM_INFO testbench.sv(198) @ 510: uvm_test_top.componentB [COMPB] componentB:TLM_Fifo is now FULL !
UVM_INFO testbench.sv(198) @ 520: uvm_test_top.componentB [COMPB] componentB:TLM_Fifo is now FULL !
UVM_INFO testbench.sv(198) @ 530: uvm_test_top.componentB [COMPB] componentB:TLM_Fifo is now FULL !
UVM_INFO testbench.sv(198) @ 540: uvm_test_top.componentB [COMPB] componentB:TLM_Fifo is now FULL !
UVM_INFO testbench.sv(198) @ 550: uvm_test_top.componentB [COMPB] componentB:TLM_Fifo is now FULL !
UVM_INFO testbench.sv(198) @ 560: uvm_test_top.componentB [COMPB] componentB:TLM_Fifo is now FULL !
UVM_INFO testbench.sv(198) @ 570: uvm_test_top.componentB [COMPB] componentB:TLM_Fifo is now FULL !
UVM_INFO testbench.sv(198) @ 580: uvm_test_top.componentB [COMPB] componentB:TLM_Fifo is now FULL !
UVM_INFO testbench.sv(198) @ 590: uvm_test_top.componentB [COMPB] componentB:TLM_Fifo is now FULL !
UVM_INFO testbench.sv(154) @ 600: uvm_test_top.componentB.m_subcomp_3 [SUBCOMP3] Packet received from componentA
pkt: ([email protected]) { addr: 'hab  data: 'ha8  } 
UVM_INFO testbench.sv(154) @ 800: uvm_test_top.componentB.m_subcomp_3 [SUBCOMP3] Packet received from componentA
pkt: ([email protected]) { addr: 'h77  data: 'he7  } 
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_objection.svh(1271) @ 1000: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_report_server.svh(847) @ 1000: reporter [UVM/REPORT/SERVER] 
--- UVM Report Summary ---