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 |
|
Passive |
|
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 fromuvm_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 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 ---