A UVM event pool (uvm_event_pool) stores uvm_event objects indexed by descriptive string names. Any component, sequence or module can look up an event by name, so one part of the testbench can trigger an event and any number of others can wait on it without holding handles to each other.

8 min read | Intermediate Level

What You'll Learn

  • What uvm_event_pool is and how it relates to uvm_pool
  • Add, look up and iterate over named events in the global pool
  • Why get() and get_global() silently create missing events
  • Share a reset event between an agent and an environment

What is a UVM Event Pool?

An event object (uvm_event) describes synchronization between processes that run at the same time. One process triggers the event, and any number of other processes block until it happens. The difficulty in a large testbench is getting the same event handle to every component that needs it.

The event pool solves this by storing events under string names, much like finding a file in a folder by its name. The events stored inside the pool are instances of uvm_event#(uvm_object).

uvm_event_pool class hierarchy

The pool is a specialization of the generic UVM associative array class uvm_pool #(KEY,T). A uvm_pool is a class-based dynamic associative array. Its entries are allocated on demand, and the whole array can be passed and stored by reference, which a plain SystemVerilog associative array cannot.

Definition


class uvm_object_string_pool #(type T=uvm_object) extends uvm_pool #(string,T);

typedef uvm_object_string_pool #(uvm_event#(uvm_object)) uvm_event_pool;

uvm_event_pool is a typedef of uvm_object_string_pool, which uses string keys. Its get() method has one important difference from the generic uvm_pool: if the key is not found, it creates a new object of type T, named after the key, and stores it in the pool.

UVM provides one global event pool, returned by uvm_event_pool::get_global_pool(). This is the pool most testbenches use, since every component can reach it.

Methods

Function NameFunction PrototypeDescription
addvirtual function void add (KEY key, T item)Adds the given key / item pair to the pool. If the key already exists, its item is replaced.
getvirtual function T get (KEY key)Returns the pool entry at the specified key. For uvm_event_pool, if no entry exists, a new event named key is created, stored and returned.
numvirtual function int num ()Returns the number of uniquely keyed items stored in the pool.
deletevirtual function void delete (KEY key)Removes the item with the given key from the pool.
existsvirtual function int exists (KEY key)Returns 1 if an item with the given key exists in the pool, and 0 otherwise. It never creates an entry.
get_global_poolstatic function this_type get_global_pool ()Returns a reference to the singleton global pool.
get_globalstatic function T get_global (KEY key)Returns the item for key from the global pool, creating it if it does not exist.
get_type_namevirtual function string get_type_name ()Returns the type name of the pool object.
firstvirtual function int first (ref KEY key)Sets key to the first key in the pool and returns 1. If the pool is empty, the key is unchanged and 0 is returned.
lastvirtual function int last (ref KEY key)Sets key to the last key in the pool. Returns 1 if the pool is non-empty, and 0 if it is empty.
nextvirtual function int next (ref KEY key)Sets key to the next key after it. Returns 1 if a next key is found.
prevvirtual function int prev (ref KEY key)Sets key to the key before it. Returns 1 if a previous key is found.

Where are Event Pools Used?

System-level environments use the global event pool for testbench-wide milestones such as "reset_done", "init_done" or "dut_idle". The agent that detects the milestone triggers it, and sequences, scoreboards and coverage collectors in any part of the hierarchy look it up by name. Nobody has to pass handles through the configuration database.

Example

This example creates five events, shuffles them, and adds them to the global pool in that random order. It then looks up one event by name and walks through the pool with first(), next(), prev() and last().


module tb;
	initial begin
        string 			l_key;
      	uvm_event 		l_evt;
    	uvm_event 		l_evt_q [$];
    	uvm_event_pool 	l_evt_pool = uvm_event_pool::get_global_pool();
      
        // Add each event into the global pool
        for (byte i = 0; i < 5; i++) begin
          automatic uvm_event l_evt = new($sformatf("l_evt_%0d", i));
          l_evt_q.push_back(l_evt);
   		end
      
      	// Shuffle to reorder the items
      	l_evt_q.shuffle();
      
      	foreach (l_evt_q[i]) begin        
          l_evt_pool.add(l_evt_q[i].get_name(), l_evt_q[i]);
          
          if (l_evt_pool.exists(l_evt_q[i].get_name()))
            `uvm_info("tb_top", $sformatf("%s added to pool #num=%0d", l_evt_q[i].get_name(), l_evt_pool.num()), UVM_MEDIUM)
      	end
      
        // Get a specific handle from the global pool
      	l_evt = l_evt_pool.get_global("l_evt_3");
        `uvm_info("tb_top", $sformatf("Got item %s", l_evt.get_name()), UVM_MEDIUM)
      	
        l_evt_pool.first(l_key);
          `uvm_info("tb_top", $sformatf("First item=%s", l_key), UVM_MEDIUM)
          
        while (l_evt_pool.next(l_key)) begin        
            `uvm_info("tb_top", $sformatf("Next item=%s", l_key), UVM_MEDIUM)
        end
          
          while (l_evt_pool.prev(l_key)) begin        
          `uvm_info("tb_top", $sformatf("Prev item=%s", l_key), UVM_MEDIUM)
        end
          
        l_evt_pool.last(l_key);
        `uvm_info("tb_top", $sformatf("Last item=%s", l_key), UVM_MEDIUM)          		
          
    end
endmodule
Output
xcelium> run          
UVM_INFO testbench.sv(29) @ 0: reporter [tb_top] l_evt_2 added to pool #num=1
UVM_INFO testbench.sv(29) @ 0: reporter [tb_top] l_evt_1 added to pool #num=2
UVM_INFO testbench.sv(29) @ 0: reporter [tb_top] l_evt_4 added to pool #num=3
UVM_INFO testbench.sv(29) @ 0: reporter [tb_top] l_evt_0 added to pool #num=4
UVM_INFO testbench.sv(29) @ 0: reporter [tb_top] l_evt_3 added to pool #num=5
UVM_INFO testbench.sv(34) @ 0: reporter [tb_top] Got item l_evt_3
UVM_INFO testbench.sv(37) @ 0: reporter [tb_top] First item=l_evt_0
UVM_INFO testbench.sv(40) @ 0: reporter [tb_top] Next item=l_evt_1
UVM_INFO testbench.sv(40) @ 0: reporter [tb_top] Next item=l_evt_2
UVM_INFO testbench.sv(40) @ 0: reporter [tb_top] Next item=l_evt_3
UVM_INFO testbench.sv(40) @ 0: reporter [tb_top] Next item=l_evt_4
UVM_INFO testbench.sv(44) @ 0: reporter [tb_top] Prev item=l_evt_3
UVM_INFO testbench.sv(44) @ 0: reporter [tb_top] Prev item=l_evt_2
UVM_INFO testbench.sv(44) @ 0: reporter [tb_top] Prev item=l_evt_1
UVM_INFO testbench.sv(44) @ 0: reporter [tb_top] Prev item=l_evt_0
UVM_INFO testbench.sv(48) @ 0: reporter [tb_top] Last item=l_evt_4
xmsim: *W,RNQUIE: Simulation is complete.          

The events were added in the shuffled order 2, 1, 4, 0, 3, as the num() count shows. The iteration methods, however, return the keys in sorted string order, from l_evt_0 to l_evt_4. This is because the pool is a SystemVerilog associative array with string keys, which is always ordered by key.

Quick Check

Q: After the example runs, the code calls l_evt_pool.get_global("l_evt_9"). What does num() return afterwards?

Show Answer

6. No event named l_evt_9 existed, so get_global() created one and added it to the pool.

Note that get_global() will create an event by that key if it does not exist already!


  // This function internally calls get() and if no item exists by 
  // that key, a new item is created with that key and returned.
          
  static function T get_global (KEY key);
    this_type gpool;
    gpool = get_global_pool(); 
    return gpool.get(key);
  endfunction

So it is recommended to check whether the key exists and report an error if it does not.


    if (! l_evt_pool.exists("event_name"))
      `uvm_error("ERR", "event_name does not exist in uvm_event_pool !")
    else
       l_evt = l_evt_pool.get_global("event_name");

Usecase Example

In the example below, the agent runs a task that monitors the negative edge of reset and triggers an event. The agent adds this event to the global uvm_event_pool, and any component that looks up the same name gets the same event handle and can wait on it.

Agent and environment sharing a reset event through the global uvm_event_pool

interface my_if;
  logic 	rstn;
endinterface

// The agent class monitors the interface and triggers the event "evt_rstn" to
// let any component that is listening to this event to respond. For example,
// a scoreboard can listen to this event and reset its internal states.
class my_agent extends uvm_agent;
  `uvm_component_utils(my_agent)
  
  virtual my_if  	vif;
  uvm_event_pool 	m_pool;
  uvm_event 		m_evt_rstn;
  
  function new(string name="my_agent", uvm_component parent=null);
    super.new(name, parent);
  endfunction
  
  virtual function void build_phase(uvm_phase phase);
    uvm_event_pool 		l_pool;
    
    super.build_phase(phase);
    
    if (! uvm_config_db #(virtual my_if)::get(null, "*", "vif", vif))
      `uvm_fatal(get_type_name(), $sformatf("[NOVIF] vif not found !"))
          
    m_evt_rstn = new();
        
    l_pool = uvm_event_pool::get_global_pool();
    l_pool.add("m_evt_rstn", m_evt_rstn);
  endfunction
      
  virtual task run_phase(uvm_phase phase);
    super.run_phase(phase);    
    fork
      reset_tracker();
    join_none
  endtask
    
  virtual task reset_tracker();
    `uvm_info (get_type_name(), "[reset_tracker] Start reset monitor", UVM_MEDIUM)
    forever begin
  	  @(negedge vif.rstn);
      m_evt_rstn.trigger();
    end      
  endtask
    
endclass

// The environment class receives the reset monitor trigger
class my_env extends uvm_env;
  `uvm_component_utils(my_env)
  
  my_agent 	m_agent;
  
  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);
    
    m_agent = my_agent::type_id::create("m_agent", this);
  endfunction
  
  virtual task run_phase(uvm_phase phase);
    super.run_phase(phase);
    
    fork
      track();
    join_none
  endtask

  virtual task track();
    uvm_event 	l_evt_rstn;
    
    // get_global() never returns null (it creates a missing event), so check exists() first
    if (! uvm_event_pool::get_global_pool().exists("m_evt_rstn"))
      `uvm_fatal(get_type_name(), "Did not find m_evt_rstn")
    else begin
      l_evt_rstn = uvm_event_pool::get_global("m_evt_rstn");
      `uvm_info(get_type_name(), $sformatf("Found item %s", l_evt_rstn.get_name()), UVM_MEDIUM)
    end
    
    `uvm_info(get_type_name(), "[track] Start tracker", UVM_MEDIUM)
    forever begin
      l_evt_rstn.wait_trigger();
      `uvm_info(get_type_name(), $sformatf("[track] Reset found"), UVM_MEDIUM)
    end
  endtask  
endclass

// Lets define a test class to stimulate the reset signal
class my_test extends uvm_test;
  `uvm_component_utils (my_test)
  
  my_env 		 	m_env;
  virtual my_if  	vif;
  
  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);    
    m_env = my_env::type_id::create("my_env", this);
    
    if (! uvm_config_db #(virtual my_if)::get(null, "*", "vif", vif))
      `uvm_fatal(get_type_name(), $sformatf("[NOVIF] vif not found !"))
  endfunction
  
  virtual task run_phase(uvm_phase phase);        
    super.run_phase(phase);
    phase.raise_objection(this);        
    stimulus();    
    phase.drop_objection(this);
  endtask
    
  virtual task stimulus();
    #10 vif.rstn = 1;
    #20 vif.rstn = 0;
    #100 vif.rstn = 1;
    #200 vif.rstn = 0;
    #400;
  endtask    
endclass
Output
UVM_INFO @ 0: reporter [RNTST] Running test my_test...
UVM_INFO testbench.sv(83) @ 0: uvm_test_top.my_env [my_env] [track] Start tracker
UVM_INFO testbench.sv(47) @ 0: uvm_test_top.my_env.m_agent [my_agent] [reset_tracker] Start reset monitor
UVM_INFO testbench.sv(86) @ 30: uvm_test_top.my_env [my_env] [track] Reset found
UVM_INFO testbench.sv(86) @ 330: uvm_test_top.my_env [my_env] [track] Reset found
UVM_INFO /xcelium23.09/tools/methodology/UVM/CDNS-1.2/sv/src/base/uvm_objection.svh(1271) @ 730: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase
UVM_INFO /xcelium23.09/tools/methodology/UVM/CDNS-1.2/sv/src/base/uvm_report_server.svh(847) @ 730: reporter [UVM/REPORT/SERVER] 
--- UVM Report Summary ---

The test drives reset low at time 30 and again at time 330. Each negative edge triggers m_evt_rstn in the agent, and the environment, which looked up the same name, prints "Reset found" at exactly those times.

Where is a Shared Reset Event Used?

Scoreboards and reference models must clear their expected-transaction queues when the DUT is reset in the middle of a test. Listening to a pool event lets them react to every reset without a direct connection to the agent that watches the reset pin.

Common Mistakes

Mistake #1: Checking for null after get() or get_global()

Wrong: l_evt = uvm_event_pool::get_global("rst_done"); if (l_evt == null) ...
Right: if (!uvm_event_pool::get_global_pool().exists("rst_done")) ...

Why? For an event pool, get() and get_global() never return null. They create a new event when the key is missing, so a null check can never catch a missing or misspelled name.

Mistake #2: A typo in the event name

If the producer uses "m_evt_rstn" and a consumer asks for "m_evt_rst", the consumer silently gets a brand-new event that nobody triggers, and its wait hangs until the test times out. Keys are case-sensitive strings, so define them once, for example as localparam string constants in a package, and use the constants everywhere.

Mistake #3: Mixing new() + add() with get_global() in the wrong order

Wrong: a consumer calls get_global("cfg_done") before the producer runs cfg_done = new(); pool.add("cfg_done", cfg_done);
Right: have both the producer and the consumers call uvm_event_pool::get_global("cfg_done")

Why? The consumer's early call creates one event, and the producer's add() then replaces it with a different event. The producer triggers its own event, while the consumer waits forever on the old one. When everyone uses get_global(), the first caller creates the event and all later callers get the same handle, in any order.

Practice Exercise

Challenge: Share a "cfg_done" Event Safely

A configuration sequence must signal when DUT programming is finished, and a scoreboard must not start checking before that. Use the global event pool so the two do not need handles to each other. The solution must work whichever side looks up the event first, and the scoreboard must not hang if it starts after configuration has already finished.

Hint

Put the key in one shared constant, use get_global() on both sides, and pick the wait method from the UVM Event article that returns immediately if the event is already on.

Solution

package tb_events_pkg;
  localparam string CFG_DONE = "cfg_done";     // One definition of the key, no typos
endpackage

// In the configuration sequence
virtual task body ();
  uvm_event cfg_done = uvm_event_pool::get_global (tb_events_pkg::CFG_DONE);
  // Program DUT registers here
  cfg_done.trigger ();                         // Event turns "on"
endtask

// In the scoreboard
virtual task run_phase (uvm_phase phase);
  uvm_event cfg_done = uvm_event_pool::get_global (tb_events_pkg::CFG_DONE);
  cfg_done.wait_on ();                         // Returns at once if config already finished
  // Start checking transactions here
endtask

Both sides call get_global(), so whichever runs first creates the event and the other receives the same handle. wait_on() covers the case where the scoreboard starts after the trigger.

Summary

In this tutorial, you learned:

  • uvm_event_pool is a typedef of uvm_object_string_pool #(uvm_event#(uvm_object)), built on uvm_pool
  • The global pool from get_global_pool() lets any part of the testbench share events by name
  • get() and get_global() create a new event if the key is missing, and never return null
  • exists() is the only safe way to check whether an event is already in the pool
  • Iteration with first(), next(), prev() and last() follows sorted key order, not insertion order
  • Using get_global() on both producer and consumer sides avoids order-dependent handle mismatches
Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5