UVM Subscriber

A UVM subscriber is a specialized component derived from uvm_subscriber that is primarily used to receive and process transactions. It is often utilized in scoreboards, coverage collectors, or monitors to analyze simulation data. Mostly we use uvm_subscriber for functional coverage monitor.

A subscriber typically:

  • Connects to an analysis port to receive transactions
  • Stores, compares, or checks incoming data
  • Is used for checking functional coverage, logging, or verification.

Unlike a monitor, which captures DUT activity and forwards data to multiple components, a subscriber’s primary role is to consume and process transactions.

Structure of a UVM Subscriber

A user-defined subscriber class is extended from uvm_subsriber. uvm_subscriber is inherited by uvm_component. A UVM subscriber typically consists of:

  • TLM Analysis Export (analysis_export): Receives transactions from another UVM component (such as a monitor or scoreboard).
  • Write Method (write() function): Processes incoming transactions when received.
  • Storage (Optional): Keeps track of received data for further comparison or analysis.

Example: Implementing a UVM Subscriber

Let’s walk through a simple UVM subscriber that collects transaction data and prints it for debugging.

Step 1: Define a Transaction Class

First, we create a basic transaction class that the subscriber will receive.

class transaction extends uvm_sequence_item;
  rand bit [7:0] data;

  `uvm_object_utils(transaction)

  function new(string name = "transaction");
    super.new(name);
  endfunction

  function void do_print(uvm_printer printer);
    printer.print_field("data", data, 8, UVM_DEC);
  endfunction
endclass

Step 2: Create a UVM Subscriber to Collect Coverage

In this step, we define a UVM subscriber that collects functional coverage every time it receives a transaction.

class coverage_subscriber extends uvm_subscriber#(transaction);
  
  `uvm_component_utils(coverage_subscriber)
  
  transaction trans;

  // Functional coverage collection
  covergroup transaction_cg;
    coverpoint trans.data {
      bins low_range  = {[0:63]};
      bins mid_range  = {[64:127]};
      bins high_range = {[128:255]};
    }
  endgroup

  function new(string name = "coverage_subscriber", uvm_component parent = null);
    super.new(name, parent);
    transaction_cg = new();
  endfunction
  
  function void build_phase(uvm_phase phase);
      super.build_phase(phase);
      $display("======in the coverage file==========");
    trans = transaction::type_id::create("trans",this);
    endfunction

  // Write method to collect coverage
  function void write(transaction trans);
    transaction_cg.sample();
    `uvm_info("COVERAGE_SUBSCRIBER", $sformatf("Received transaction: Data = %0d, Coverage of trans signal= %0d %%", trans.data,transaction_cg.get_coverage()), UVM_MEDIUM)
  endfunction

endclass

Step 3: Connect Subscriber in a UVM Environment

Now, let’s integrate the subscriber into a UVM environment.

class my_env extends uvm_env;
  
  `uvm_component_utils(my_env)

  uvm_analysis_port#(transaction) ap; 
  coverage_subscriber cov_sub;  // Coverage subscriber instance

  function new(string name = "my_env", uvm_component parent = null);
    super.new(name, parent);
  endfunction

  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    cov_sub = coverage_subscriber::type_id::create("cov_sub", this);
    ap = new("ap", this);
  endfunction

  function void connect_phase(uvm_phase phase);
    ap.connect(cov_sub.analysis_export); // Connect subscriber to analysis port
  endfunction

endclass

Step 4: Create a UVM Test to Generate Transactions

We now create a test to generate random transactions and send them to the subscriber for coverage collection.

class my_test extends uvm_test;
  
  `uvm_component_utils(my_test)

  my_env env;

  function new(string name = "my_test", uvm_component parent = null);
    super.new(name, parent);
  endfunction

  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    env = my_env::type_id::create("env", this);
  endfunction

  task run_phase(uvm_phase phase);
    transaction t;
    phase.raise_objection(this);
    
    for (int i = 0; i < 10; i++) begin
      t = transaction::type_id::create("t");
      t.randomize();
      env.ap.write(t); // Send transaction to the coverage subscriber
      #10;
    end
    
    phase.drop_objection(this);
  endtask

endclass

Simulation Output

When the test runs, the subscriber receives transactions and collects coverage. You will see output similar to this:

UVM_INFO @ 0: reporter [RNTST] Running test my_test...
======in the coverage file==========
UVM_INFO subscriber.sv(30) @ 0: uvm_test_top.env.cov_sub [COVERAGE_SUBSCRIBER] Received transaction: Data = 50, Coverage of trans signal= 33 %
UVM_INFO subscriber.sv(30) @ 10: uvm_test_top.env.cov_sub [COVERAGE_SUBSCRIBER] Received transaction: Data = 38, Coverage of trans signal= 33 %
UVM_INFO subscriber.sv(30) @ 20: uvm_test_top.env.cov_sub [COVERAGE_SUBSCRIBER] Received transaction: Data = 25, Coverage of trans signal= 33 %
UVM_INFO subscriber.sv(30) @ 30: uvm_test_top.env.cov_sub [COVERAGE_SUBSCRIBER] Received transaction: Data = 0, Coverage of trans signal= 33 %
UVM_INFO subscriber.sv(30) @ 40: uvm_test_top.env.cov_sub [COVERAGE_SUBSCRIBER] Received transaction: Data = 218, Coverage of trans signal= 33 %
UVM_INFO subscriber.sv(30) @ 50: uvm_test_top.env.cov_sub [COVERAGE_SUBSCRIBER] Received transaction: Data = 91, Coverage of trans signal= 33 %
UVM_INFO subscriber.sv(30) @ 60: uvm_test_top.env.cov_sub [COVERAGE_SUBSCRIBER] Received transaction: Data = 21, Coverage of trans signal= 33 %
UVM_INFO subscriber.sv(30) @ 70: uvm_test_top.env.cov_sub [COVERAGE_SUBSCRIBER] Received transaction: Data = 93, Coverage of trans signal= 33 %
UVM_INFO subscriber.sv(30) @ 80: uvm_test_top.env.cov_sub [COVERAGE_SUBSCRIBER] Received transaction: Data = 53, Coverage of trans signal= 33 %
UVM_INFO subscriber.sv(30) @ 90: uvm_test_top.env.cov_sub [COVERAGE_SUBSCRIBER] Received transaction: Data = 197, Coverage of trans signal= 33 %
UVM_INFO /apps/vcsmx/vcs/U-2023.03-SP2//etc/uvm-1.2/src/base/uvm_objection.svh(1276) @ 100: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase
UVM_INFO /apps/vcsmx/vcs/U-2023.03-SP2//etc/uvm-1.2/src/base/uvm_report_server.svh(904) @ 100: reporter [UVM/REPORT/SERVER] 
--- UVM Report Summary ---

** Report counts by severity
UVM_INFO :   13
UVM_WARNING :    0
UVM_ERROR :    0
UVM_FATAL :    0
** Report counts by id
[COVERAGE_SUBSCRIBER]    10
[RNTST]     1
[TEST_DONE]     1
[UVM/RELNOTES]     1

$finish called from file "/apps/vcsmx/vcs/U-2023.03-SP2//etc/uvm-1.2/src/base/uvm_root.svh", line 527.
$finish at simulation time 

By integrating functional coverage into a UVM subscriber, we can track the effectiveness of test patterns and ensure all important scenarios are verified.