Subscribers are basically listeners of an analysis port. They subscribe to a broadcaster and receive objects whenever an item is broadcasted via the connected analysis port. A
uvm_component class does not have an in-built analysis port, while a
uvm_subscriber is an extended version with an analysis port named
virtual class uvm_subscriber #(type T=int) extends uvm_component; typedef uvm_subscriber #(T) this_type; uvm_analysis_imp #(T, this_type) analysis_export; function new (string name, uvm_component parent); super.new (name, parent); analysis_export = new ("analysis_imp", this); endfunction pure virtual function void write (T, t); endclass
In a typical case, an agent would have a TLM analysis port for it's monitor to share the data object it collected on the agent's interface with other testbench components. So it is easier to create a user class inherited from
uvm_subscriber and use the in-built
analysis_export implementation to connect to the analysis port of the agent. For example, you could have functional coverage groups and coverpoints in a subscriber and have that sampled whenever it receives an object from the agent.
class my_coverage extends uvm_subscriber #(bus_pkt); covergroup cg_bus; ... endgroup virtual function void write (bus_pkt pkt); cg_bus.sample (); endfunction endclass class my_env extends uvm_env; ... virtual function void connect_phase (uvm_phase phase); super.connect_phase (phase); my_agent.custom_ap.connect (my_cov.analysis_export); endfunction endclass
It is not mandatory to inherit from
uvm_subscriber, but it is a recommended practice so that there exists a uniform way through which a component can connect with an analysis port.