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 analysis_export.

Class definition


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

Use Case

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.

uvm_subscriber_in_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.