A UVM TLM analysis port is a broadcast connection. A component calls write() on its uvm_analysis_port, and the port calls the write() function of every component connected to it, whether that is none, one or many. It is how monitors share what they observe with scoreboards, coverage collectors and any other component that wants a copy, without knowing who those components are.
Analysis ports are the most widely used TLM connection in real testbenches. Almost every agent has one on its monitor, and almost every scoreboard and coverage collector is fed by one.
7 min read | Intermediate Level
What You'll Learn
- Explain how an analysis port differs from put and get connections
- Declare an analysis port and broadcast transactions with
write() - Receive transactions in a subscriber through
analysis_export - Connect one analysis port to several subscribers
How an Analysis Port Differs
Put and get ports expect exactly one connection, and the call can block. An analysis port is different in two ways:
- Any number of connections. The port keeps a list of everything connected to it, and
write()calls each one in turn. With nothing connected,write()simply does nothing, so a monitor works the same whether or not anyone is listening. - Never blocks.
write()is avoidfunction, so it completes in zero simulation time. A monitor can broadcast and immediately go back to sampling its interface.
Syntax
uvm_analysis_port #(T) ap; // in the broadcaster
uvm_analysis_imp #(T, IMP) imp; // in a receiver; IMP implements write()
ap.write(t); // broadcast t to all connected receivers
virtual function void write(T t); // what each receiver implements
Receivers derived from uvm_subscriber #(T) already have a uvm_analysis_imp called analysis_export, so they only need to implement write().
Example: One Broadcaster, Three Subscribers
This example extends the blocking put example. compA sends five packets to compB through a put port. Each time compB receives a packet in its put() task, it prints it and broadcasts it through an analysis port to three subscribers.
The Broadcaster
class componentB extends uvm_component;
`uvm_component_utils(componentB)
uvm_blocking_put_imp #(simple_packet, componentB) put_export; // receives from compA
uvm_analysis_port #(simple_packet) ap; // broadcasts to subscribers
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);
put_export = new("put_export", this);
ap = new("analysis_port", this); // instance name used in the topology
endfunction
// Called for every packet compA puts
virtual task put(simple_packet pkt);
`uvm_info("COMPB", "Packet received from CompA", UVM_LOW)
pkt.print();
ap.write(pkt); // broadcast; returns in zero time
endtask
endclass
compA is the sender from UVM TLM Blocking Put Port, with a uvm_blocking_put_port named put_port and a loop that sends five randomized packets.
The Subscriber
Each subscriber extends uvm_subscriber for the packet type and implements write(). The base class already declares the analysis imp:
// How uvm_subscriber is declared in the library (simplified)
virtual class uvm_subscriber #(type T = int) extends uvm_component;
typedef uvm_subscriber #(T) this_type;
uvm_analysis_imp #(T, this_type) analysis_export;
pure virtual function void write(T t);
endclass
class sub extends uvm_subscriber #(simple_packet);
`uvm_component_utils(sub)
function new(string name = "sub", uvm_component parent = null);
super.new(name, parent);
endfunction
// Called once for every ap.write() in componentB
virtual function void write(simple_packet t);
`uvm_info(get_full_name(), "Sub got transaction", UVM_MEDIUM)
endfunction
endclass
The argument is named t on purpose. write() overrides a virtual method declared in uvm_subscriber, and SystemVerilog requires an override to use identical argument names. Some simulators only warn about a different name, but others reject it.
Connecting Everything
All five components live in the environment, so it makes every connection. The same analysis port is connected three times, once to each subscriber.
class my_env extends uvm_env;
`uvm_component_utils(my_env)
componentA compA;
componentB compB;
sub sub1, sub2, sub3;
function new(string name = "my_env", 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("compA", this);
compB = componentB::type_id::create("compB", this);
sub1 = sub::type_id::create("sub1", this);
sub2 = sub::type_id::create("sub2", this);
sub3 = sub::type_id::create("sub3", this);
endfunction
virtual function void connect_phase(uvm_phase phase);
compA.put_port.connect(compB.put_export);
// One analysis port, three subscribers
compB.ap.connect(sub1.analysis_export);
compB.ap.connect(sub2.analysis_export);
compB.ap.connect(sub3.analysis_export);
endfunction
endclass
----------------------------------------------------------------
CDNS-UVM-1.1d (15.10-s004)
(C) 2007-2013 Mentor Graphics Corporation
(C) 2007-2013 Cadence Design Systems, Inc.
(C) 2006-2013 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
----------------------------------------------------------------
UVM_INFO @ 0: reporter [RNTST] Running test base_test...
UVM_INFO @ 0: reporter [UVMTOP] UVM testbench topology:
-------------------------------------------------------
Name Type Size Value
-------------------------------------------------------
uvm_test_top base_test - @2602
m_top_env my_env - @2668
compA componentA - @2699
put_port uvm_blocking_put_port - @3015
compB componentB - @2729
analysis_port uvm_analysis_port - @3115
put_export uvm_blocking_put_imp - @3066
sub1 sub - @2759
analysis_imp uvm_analysis_imp - @2808
sub2 sub - @2839
analysis_imp uvm_analysis_imp - @2888
sub3 sub - @2869
analysis_imp uvm_analysis_imp - @2966
-------------------------------------------------------
UVM_INFO ./tb/tlm.sv(61) @ 0: uvm_test_top.m_top_env.compA [COMPA] Packet sent to CompB
pkt: (simple_packet@3205) { addr: 'h2f data: 'h64 rwb: 'h0 }
UVM_INFO ./tb/tlm.sv(91) @ 0: uvm_test_top.m_top_env.compB [COMPB] Packet received from CompA
----------------------------------
Name Type Size Value
----------------------------------
pkt simple_packet - @3205
addr integral 8 'h2f
data integral 8 'h64
rwb integral 1 'h0
----------------------------------
UVM_INFO ./tb/tlm.sv(122) @ 0: uvm_test_top.m_top_env.sub1 [uvm_test_top.m_top_env.sub1] Sub got transaction
UVM_INFO ./tb/tlm.sv(122) @ 0: uvm_test_top.m_top_env.sub2 [uvm_test_top.m_top_env.sub2] Sub got transaction
UVM_INFO ./tb/tlm.sv(122) @ 0: uvm_test_top.m_top_env.sub3 [uvm_test_top.m_top_env.sub3] Sub got transaction
UVM_INFO ./tb/tlm.sv(61) @ 0: uvm_test_top.m_top_env.compA [COMPA] Packet sent to CompB
pkt: (simple_packet@3198) { addr: 'hb8 data: 'h27 rwb: 'h0 }
UVM_INFO ./tb/tlm.sv(91) @ 0: uvm_test_top.m_top_env.compB [COMPB] Packet received from CompA
----------------------------------
Name Type Size Value
----------------------------------
pkt simple_packet - @3198
addr integral 8 'hb8
data integral 8 'h27
rwb integral 1 'h0
----------------------------------
UVM_INFO ./tb/tlm.sv(122) @ 0: uvm_test_top.m_top_env.sub1 [uvm_test_top.m_top_env.sub1] Sub got transaction
UVM_INFO ./tb/tlm.sv(122) @ 0: uvm_test_top.m_top_env.sub2 [uvm_test_top.m_top_env.sub2] Sub got transaction
UVM_INFO ./tb/tlm.sv(122) @ 0: uvm_test_top.m_top_env.sub3 [uvm_test_top.m_top_env.sub3] Sub got transaction
UVM_INFO ./tb/tlm.sv(61) @ 0: uvm_test_top.m_top_env.compA [COMPA] Packet sent to CompB
pkt: (simple_packet@3170) { addr: 'he3 data: 'h29 rwb: 'h0 }
UVM_INFO ./tb/tlm.sv(91) @ 0: uvm_test_top.m_top_env.compB [COMPB] Packet received from CompA
----------------------------------
Name Type Size Value
----------------------------------
pkt simple_packet - @3170
addr integral 8 'he3
data integral 8 'h29
rwb integral 1 'h0
----------------------------------
UVM_INFO ./tb/tlm.sv(122) @ 0: uvm_test_top.m_top_env.sub1 [uvm_test_top.m_top_env.sub1] Sub got transaction
UVM_INFO ./tb/tlm.sv(122) @ 0: uvm_test_top.m_top_env.sub2 [uvm_test_top.m_top_env.sub2] Sub got transaction
UVM_INFO ./tb/tlm.sv(122) @ 0: uvm_test_top.m_top_env.sub3 [uvm_test_top.m_top_env.sub3] Sub got transaction
UVM_INFO ./tb/tlm.sv(61) @ 0: uvm_test_top.m_top_env.compA [COMPA] Packet sent to CompB
pkt: (simple_packet@3175) { addr: 'h85 data: 'hbd rwb: 'h0 }
UVM_INFO ./tb/tlm.sv(91) @ 0: uvm_test_top.m_top_env.compB [COMPB] Packet received from CompA
----------------------------------
Name Type Size Value
----------------------------------
pkt simple_packet - @3175
addr integral 8 'h85
data integral 8 'hbd
rwb integral 1 'h0
----------------------------------
UVM_INFO ./tb/tlm.sv(122) @ 0: uvm_test_top.m_top_env.sub1 [uvm_test_top.m_top_env.sub1] Sub got transaction
UVM_INFO ./tb/tlm.sv(122) @ 0: uvm_test_top.m_top_env.sub2 [uvm_test_top.m_top_env.sub2] Sub got transaction
UVM_INFO ./tb/tlm.sv(122) @ 0: uvm_test_top.m_top_env.sub3 [uvm_test_top.m_top_env.sub3] Sub got transaction
UVM_INFO ./tb/tlm.sv(61) @ 0: uvm_test_top.m_top_env.compA [COMPA] Packet sent to CompB
pkt: (simple_packet@3192) { addr: 'hdf data: 'hab rwb: 'h0 }
UVM_INFO ./tb/tlm.sv(91) @ 0: uvm_test_top.m_top_env.compB [COMPB] Packet received from CompA
----------------------------------
Name Type Size Value
----------------------------------
pkt simple_packet - @3192
addr integral 8 'hdf
data integral 8 'hab
rwb integral 1 'h0
----------------------------------
UVM_INFO ./tb/tlm.sv(122) @ 0: uvm_test_top.m_top_env.sub1 [uvm_test_top.m_top_env.sub1] Sub got transaction
UVM_INFO ./tb/tlm.sv(122) @ 0: uvm_test_top.m_top_env.sub2 [uvm_test_top.m_top_env.sub2] Sub got transaction
UVM_INFO ./tb/tlm.sv(122) @ 0: uvm_test_top.m_top_env.sub3 [uvm_test_top.m_top_env.sub3] Sub got transaction
--- UVM Report catcher Summary ---
Number of demoted UVM_FATAL reports : 0
Number of demoted UVM_ERROR reports : 0
Number of demoted UVM_WARNING reports: 0
Number of caught UVM_FATAL reports : 0
Number of caught UVM_ERROR reports : 0
Number of caught UVM_WARNING reports : 0
--- UVM Report Summary ---
** Report counts by severity
UVM_INFO : 27
UVM_WARNING : 0
UVM_ERROR : 0
UVM_FATAL : 0
** Report counts by id
[COMPA] 5
[COMPB] 5
[RNTST] 1
[UVMTOP] 1
[uvm_test_top.m_top_env.sub1] 5
[uvm_test_top.m_top_env.sub2] 5
[uvm_test_top.m_top_env.sub3] 5
Simulation complete via $finish(1) at time 0 FS + 179
For each of the five packets, compA sends, compB prints it, and then all three subscribers report receiving it, in the order they were connected. The report summary confirms 5 transactions per subscriber. The topology shows each subscriber's imp under the name analysis_imp: that is the instance name uvm_subscriber gives its analysis_export handle. This log was produced with UVM 1.1d, so the banner differs from UVM 1.2.
Quick Check
Q: If sub2 took 10 ns to process each transaction, could it implement that delay inside write()?
Show Answer
No. write() is a function, so it cannot contain a delay. A slow subscriber should store the transaction, for example in a queue or a uvm_tlm_analysis_fifo, and process it later in its own run_phase.
Where Analysis Ports Are Used
The classic use is the monitor in every agent. It reconstructs transactions from pin activity and writes each one to its analysis port, which is connected to a scoreboard, a coverage subscriber, and perhaps a transaction recorder. Adding a new checker later only needs one more connect() call in the environment; the monitor never changes. See UVM Monitor and Subscriber.
A receiver that needs two different analysis inputs cannot define write() twice. Use the `uvm_analysis_imp_decl macro to create imps with distinct method names, as shown in Using _decl Macro in TLM.
Analysis Exports and Analysis FIFOs
Two related classes complete the picture. A uvm_analysis_export forwards analysis transactions down the hierarchy to an imp in a child, in the same way a put export forwards put() calls. On the broadcasting side, a component such as an agent usually declares its own uvm_analysis_port and connects its monitor's port to it (port to port), so that the environment can connect subscribers to the agent without knowing the monitor inside.
// Inside an agent: expose the monitor's analysis port at the agent boundary
uvm_analysis_port #(simple_packet) ap; // created in build_phase
virtual function void connect_phase(uvm_phase phase);
m_monitor.ap.connect(this.ap); // child port to parent port
endfunction
A uvm_tlm_analysis_fifo is an unbounded FIFO with an analysis_export on its input side. Connecting an analysis port to it stores every written transaction, and a component can then pull them out with get() at its own pace. That is the standard way to feed a subscriber that needs time to process each transaction, as described in UVM TLM Fifo.
Common Mistakes
Mistake #1: Modifying a transaction after writing it
All subscribers receive the same handle. If the broadcaster changes or reuses the object after write(), every subscriber that stored the handle sees the change. Write a new object each time, or let subscribers that keep transactions store a clone.
Mistake #2: Putting time-consuming code in write()
write() must return immediately. Any processing that needs time belongs in the subscriber's own thread, fed by a queue or an analysis FIFO.
Mistake #3: Registering a parameterized subscriber with `uvm_component_utils
A subscriber declared as class sub #(type T = simple_packet) extends uvm_subscriber #(T) must use `uvm_component_param_utils(sub #(T)). Using `uvm_component_utils on a parameterized class is a registration error. Extending a concrete specialization, as above, avoids the issue entirely.
Mistake #4: Expecting write() to report whether anyone received the transaction
write() returns nothing and succeeds even with zero subscribers. If a scoreboard seems to receive nothing, the analysis port is probably not connected to it. Check the connect() calls in the environment, or print the topology, rather than looking for an error from the broadcaster.
Summary
In this article, you learned:
- An analysis port broadcasts with
write()to zero, one or many connected receivers write()is a function, so it never blocks and receivers must not consume time in it- Subscribers derived from
uvm_subscriberalready provideanalysis_exportand only implementwrite(T t) - One analysis port is connected to each receiver with a separate
connect()call - All receivers get the same object handle, so transactions should not be modified after they are written