Why Learn UVM Events? Testbench components often need to tell each other that something has happened: reset is done, a configuration sequence has finished, or an interrupt has fired. Getting this handshake wrong is one of the most common causes of tests that hang until the timeout. uvm_event gives you a synchronization object with state, data and debug support that plain SystemVerilog events do not have.

The uvm_event class extends uvm_object and adds features beyond the standard SystemVerilog event. Named events can be stored and shared through a uvm_event_pool, data can be passed along when an event is triggered, and callbacks can be registered to run on every trigger.

9 min read | Intermediate Level

What You'll Learn

  • What uvm_event adds over a SystemVerilog event
  • The difference between wait_trigger, wait_ptrigger and wait_on
  • How trigger and wait ordering causes races and hangs, shown in six cases
  • Pass data with a trigger and reset an event for reuse
uvm_event class hierarchy

The uvm_event_base class is an abstract wrapper around the SystemVerilog event construct. It adds extra functionality, such as support for callbacks and tracking the number of processes waiting on the event. The uvm_event class extends uvm_event_base and has an optional type parameter T, the type of data that can be passed when the event is triggered.

Several built-in UVM classes use uvm_event, but in your own code, use it only when you need its extra features. It has more simulation overhead than a plain SystemVerilog event.

Unlike a plain event, a uvm_event has state. Once triggered, it stays "on" until it is explicitly reset with reset(). This state is what makes wait_on() useful, as the cases below show.

Syntax


// Declare handle for uvm_event
uvm_event    <inst_name>;

// Instantiate uvm_event in build_phase of component
// or within an object
<inst_name> = new();

To pass data with the trigger, give the event a type parameter, such as uvm_event #(my_item) m_ev;. Without a parameter, T defaults to uvm_object.

Main Methods

MethodTypeDescription
trigger(data)functionTriggers the event, turns it "on", and wakes all waiting processes. The optional data argument is stored with the trigger.
wait_triggertaskWaits until the event is triggered. If wait_trigger and trigger happen in the same time step, a race condition can occur.
wait_ptriggertaskWaits for a persistent trigger. It also returns if the event was already triggered earlier in the same time step, which removes the race of wait_trigger.
wait_ontaskWaits for the event to be triggered for the first time. If the event is already "on", the task returns immediately.
wait_offtaskIf the event is already "on", waits until it is turned "off" by a reset() call. If the event has not been triggered, returns immediately.
resetfunctionTurns the event "off" and clears its trigger time and data, so it can be reused.
is_onfunctionReturns 1 if the event has been triggered and not yet reset.
get_trigger_datafunctionReturns the data passed with the last trigger().
get_trigger_timefunctionGets the time that this event was last triggered. If the event has not been triggered, or has been reset, the trigger time is 0.
get_num_waitersfunctionReturns the number of processes currently waiting on the event.

Where are UVM Events Used?

A common use is a "reset done" or "configuration done" event shared across the environment. A monitor triggers it when it sees reset deassert, and sequences, scoreboards and coverage collectors all wait on it before they start. Because the event keeps its "on" state, components that start late do not miss it.

The six cases below use the same test, with two processes forked in run_phase: process_a triggers m_event, and process_b waits for it. Only the timing and the wait method change.

Case #1: wait_trigger -> trigger

process_b starts waiting at time 0, and process_a triggers the event at time 100.


class my_test extends uvm_test;
  `uvm_component_utils(my_test)
  
  function new(string name="my_test", uvm_component parent=null);
    super.new(name, parent);
  endfunction
  
  uvm_event 	m_event;
  
  virtual function void build_phase(uvm_phase phase);    
    super.build_phase(phase);
    
    m_event = new();
  endfunction
  
  virtual task run_phase(uvm_phase phase);
    super.run_phase(phase);

    phase.raise_objection(this);
    
    fork
      process_a();
      process_b();
    join
    
    phase.drop_objection(this);
  endtask
  
  virtual task process_a();
    #100;
    `uvm_info(get_type_name(), $sformatf("[process_a] Before event.trigger"), UVM_MEDIUM)
    m_event.trigger();
    `uvm_info(get_type_name(), $sformatf("[process_a] After event.trigger"), UVM_MEDIUM)
  endtask
  
  virtual task process_b();
    `uvm_info(get_type_name(), $sformatf("[process_b] Waiting for event.trigger"), UVM_MEDIUM)
    m_event.wait_trigger();
    `uvm_info(get_type_name(), $sformatf("[process_b] Received event.trigger"), UVM_MEDIUM)    
  endtask
endclass
Output
UVM_INFO @ 0: reporter [RNTST] Running test my_test...
UVM_INFO testbench.sv(45) @ 0: uvm_test_top [my_test] [process_b] Waiting for event.trigger
UVM_INFO testbench.sv(39) @ 100: uvm_test_top [my_test] [process_a] Before event.trigger
UVM_INFO testbench.sv(41) @ 100: uvm_test_top [my_test] [process_a] After event.trigger
UVM_INFO testbench.sv(47) @ 100: uvm_test_top [my_test] [process_b] Received event.trigger
UVM_INFO /xcelium23.09/tools/methodology/UVM/CDNS-1.2/sv/src/base/uvm_objection.svh(1271) @ 100: 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) @ 100: reporter [UVM/REPORT/SERVER] 
--- UVM Report Summary ---

This is the normal case. The waiter is already blocked when the trigger arrives, so it wakes up at time 100.

Case #2: trigger -> wait_trigger

Now the trigger happens at time 0, and process_b only starts waiting at time 100.


class my_test extends uvm_test;
  `uvm_component_utils(my_test)

  // same as before  
  
  virtual task process_a();
    `uvm_info(get_type_name(), $sformatf("[process_a] Before event.trigger"), UVM_MEDIUM)
    m_event.trigger();
    `uvm_info(get_type_name(), $sformatf("[process_a] After event.trigger"), UVM_MEDIUM)
  endtask
  
  virtual task process_b();
    #100;
    `uvm_info(get_type_name(), $sformatf("[process_b] Waiting for event.trigger"), UVM_MEDIUM)
    m_event.wait_trigger();
    `uvm_info(get_type_name(), $sformatf("[process_b] Received event.trigger"), UVM_MEDIUM)    
  endtask
endclass
Output
UVM_INFO @ 0: reporter [RNTST] Running test my_test...
UVM_INFO testbench.sv(38) @ 0: uvm_test_top [my_test] [process_a] Before event.trigger
UVM_INFO testbench.sv(40) @ 0: uvm_test_top [my_test] [process_a] After event.trigger
UVM_INFO testbench.sv(45) @ 100: uvm_test_top [my_test] [process_b] Waiting for event.trigger
UVM_FATAL /xcelium23.09/tools/methodology/UVM/CDNS-1.2/sv/src/base/uvm_phase.svh(1504) @ 9200000000000: reporter [PH_TIMEOUT] Default timeout of 9200000000000 hit, indicating a probable testbench issue
UVM_INFO /xcelium23.09/tools/methodology/UVM/CDNS-1.2/sv/src/base/uvm_report_server.svh(847) @ 9200000000000: reporter [UVM/REPORT/SERVER] 
--- UVM Report Summary ---

wait_trigger() only waits for a future trigger. The trigger at time 0 was missed, no other trigger follows, and the test hangs until the phase timeout fires a UVM_FATAL.

Case #3: trigger & wait_trigger at same time

Both processes run at time 0, with no delays.


class my_test extends uvm_test;
  `uvm_component_utils(my_test)
  
  // same as before
  
  virtual task process_a();
    `uvm_info(get_type_name(), $sformatf("[process_a] Before event.trigger"), UVM_MEDIUM)
    m_event.trigger();
    `uvm_info(get_type_name(), $sformatf("[process_a] After event.trigger"), UVM_MEDIUM)
  endtask
  
  virtual task process_b();
    `uvm_info(get_type_name(), $sformatf("[process_b] Waiting for event.trigger"), UVM_MEDIUM)
    m_event.wait_trigger();
    `uvm_info(get_type_name(), $sformatf("[process_b] Received event.trigger"), UVM_MEDIUM)    
  endtask
endclass
Output
UVM_INFO @ 0: reporter [RNTST] Running test my_test...
UVM_INFO testbench.sv(38) @ 0: uvm_test_top [my_test] [process_a] Before event.trigger
UVM_INFO testbench.sv(40) @ 0: uvm_test_top [my_test] [process_a] After event.trigger
UVM_INFO testbench.sv(44) @ 0: uvm_test_top [my_test] [process_b] Waiting for event.trigger
UVM_FATAL /xcelium23.09/tools/methodology/UVM/CDNS-1.2/sv/src/base/uvm_phase.svh(1504) @ 9200000000000: reporter [PH_TIMEOUT] Default timeout of 9200000000000 hit, indicating a probable testbench issue
UVM_INFO /xcelium23.09/tools/methodology/UVM/CDNS-1.2/sv/src/base/uvm_report_server.svh(847) @ 9200000000000: reporter [UVM/REPORT/SERVER] 
--- UVM Report Summary ---

This is a race. SystemVerilog does not define which forked process runs first within a time step. In this simulator, process_a ran first, so the trigger happened before process_b started waiting and was missed. Another simulator, or a small code change, could give the opposite result.

Quick Check

Q: Cases #2 and #3 both hang. Which one is a race condition, and which is simply a missed trigger?

Show Answer

Case #3 is the race: trigger and wait happen in the same time step, and the result depends on process order. Case #2 is a missed trigger: the trigger happened 100 time units earlier, so wait_trigger() can never see it.

Case #4: wait_ptrigger -> trigger

This repeats Case #1, but process_b uses wait_ptrigger().


class my_test extends uvm_test;
  `uvm_component_utils(my_test)
  
  function new(string name="my_test", uvm_component parent=null);
    super.new(name, parent);
  endfunction
  
  // same as before
  
  virtual task process_a();
    #100;
    `uvm_info(get_type_name(), $sformatf("[process_a] Before event.trigger"), UVM_MEDIUM)
    m_event.trigger();
    `uvm_info(get_type_name(), $sformatf("[process_a] After event.trigger"), UVM_MEDIUM)
  endtask
  
  virtual task process_b();
    `uvm_info(get_type_name(), $sformatf("[process_b] Waiting for event.trigger"), UVM_MEDIUM)

    // Use persistent trigger instead
    m_event.wait_ptrigger();
    `uvm_info(get_type_name(), $sformatf("[process_b] Received event.trigger"), UVM_MEDIUM)    
  endtask
endclass
Output
UVM_INFO @ 0: reporter [RNTST] Running test my_test...
UVM_INFO testbench.sv(45) @ 0: uvm_test_top [my_test] [process_b] Waiting for event.trigger
UVM_INFO testbench.sv(39) @ 100: uvm_test_top [my_test] [process_a] Before event.trigger
UVM_INFO testbench.sv(41) @ 100: uvm_test_top [my_test] [process_a] After event.trigger
UVM_INFO testbench.sv(47) @ 100: uvm_test_top [my_test] [process_b] Received event.trigger
UVM_INFO /xcelium23.09/tools/methodology/UVM/CDNS-1.2/sv/src/base/uvm_objection.svh(1271) @ 100: 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) @ 100: reporter [UVM/REPORT/SERVER] 
--- UVM Report Summary ---

When the waiter is already blocked, wait_ptrigger() behaves exactly like wait_trigger().

Case #5: trigger -> wait_ptrigger

This repeats Case #2: the trigger happens at time 0, and process_b calls wait_ptrigger() at time 100.


class my_test extends uvm_test;
  `uvm_component_utils(my_test)
  
  // same as before

  virtual task process_a();
    
    `uvm_info(get_type_name(), $sformatf("[process_a] Before event.trigger"), UVM_MEDIUM)
    m_event.trigger();
    `uvm_info(get_type_name(), $sformatf("[process_a] After event.trigger"), UVM_MEDIUM)
  endtask
  
  virtual task process_b();
    #100;
    `uvm_info(get_type_name(), $sformatf("[process_b] Waiting for event.trigger"), UVM_MEDIUM)
    m_event.wait_ptrigger();
    `uvm_info(get_type_name(), $sformatf("[process_b] Received event.trigger"), UVM_MEDIUM)    
  endtask
endclass
Output
UVM_INFO @ 0: reporter [RNTST] Running test my_test...
UVM_INFO testbench.sv(39) @ 0: uvm_test_top [my_test] [process_a] Before event.trigger
UVM_INFO testbench.sv(41) @ 0: uvm_test_top [my_test] [process_a] After event.trigger
UVM_INFO testbench.sv(46) @ 100: uvm_test_top [my_test] [process_b] Waiting for event.trigger
UVM_FATAL /xcelium23.09/tools/methodology/UVM/CDNS-1.2/sv/src/base/uvm_phase.svh(1504) @ 9200000000000: reporter [PH_TIMEOUT] Default timeout of 9200000000000 hit, indicating a probable testbench issue
UVM_INFO /xcelium23.09/tools/methodology/UVM/CDNS-1.2/sv/src/base/uvm_report_server.svh(847) @ 9200000000000: reporter [UVM/REPORT/SERVER] 
--- UVM Report Summary ---

The test still hangs. "Persistent" only means the trigger stays visible until the end of the time step in which it happened. It does not survive into later time steps. To catch a trigger that may have happened at any earlier time, use wait_on(), which checks the event's "on" state.

Case #6: trigger & wait_ptrigger at same time

This repeats the race in Case #3, at time 100, using wait_ptrigger().


class my_test extends uvm_test;
  `uvm_component_utils(my_test)
  
  // same as before
  
  virtual task process_a();
    #100;
    `uvm_info(get_type_name(), $sformatf("[process_a] Before event.trigger"), UVM_MEDIUM)
    m_event.trigger();
    `uvm_info(get_type_name(), $sformatf("[process_a] After event.trigger"), UVM_MEDIUM)
  endtask
  
  virtual task process_b();
    #100;
    `uvm_info(get_type_name(), $sformatf("[process_b] Waiting for event.trigger"), UVM_MEDIUM)
    m_event.wait_ptrigger();
    `uvm_info(get_type_name(), $sformatf("[process_b] Received event.trigger"), UVM_MEDIUM)    
  endtask
endclass
Output
UVM_INFO @ 0: reporter [RNTST] Running test my_test...
UVM_INFO testbench.sv(39) @ 100: uvm_test_top [my_test] [process_a] Before event.trigger
UVM_INFO testbench.sv(41) @ 100: uvm_test_top [my_test] [process_a] After event.trigger
UVM_INFO testbench.sv(46) @ 100: uvm_test_top [my_test] [process_b] Waiting for event.trigger
UVM_INFO testbench.sv(48) @ 100: uvm_test_top [my_test] [process_b] Received event.trigger
UVM_INFO /xcelium23.09/tools/methodology/UVM/CDNS-1.2/sv/src/base/uvm_objection.svh(1271) @ 100: 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) @ 100: reporter [UVM/REPORT/SERVER] 
--- UVM Report Summary ---

process_a still triggers first, but wait_ptrigger() sees that the event was triggered earlier in the same time step and returns. The race in Case #3 between wait_trigger() and trigger() is resolved by using wait_ptrigger().

Summary of the Six Cases

CaseTriggerWait startsWait methodResult
#11000wait_triggerWakes at 100
#20100wait_triggerHangs (missed)
#300wait_triggerRace; hung here
#41000wait_ptriggerWakes at 100
#50100wait_ptriggerHangs (missed)
#6100100wait_ptriggerWakes at 100

In Cases #2 and #5, wait_on() would have returned immediately at time 100, because the event was still "on".

Passing Data with a Trigger

With a type parameter, trigger() can carry an object to the waiters. This avoids a separate shared variable that could be overwritten before a waiter reads it.


uvm_event #(my_item) m_item_ev = new ("m_item_ev");

// Producer side
m_item_ev.trigger (item);                        // Wake waiters and store item

// Consumer side
my_item got;
m_item_ev.wait_ptrigger ();                      // Race-safe wait
got = m_item_ev.get_trigger_data ();             // Returns the stored item, typed as my_item

This parameterized form is from UVM 1.2. In UVM 1.1, uvm_event is not parameterized, and get_trigger_data() returns a uvm_object that must be cast with $cast.

Where is Trigger Data Used?

Interrupt handling is a typical example. A monitor triggers an "interrupt" event and passes an object describing the interrupt source, and an interrupt-service sequence wakes up, reads the source from the trigger data, and runs the matching register accesses.

Common Mistakes

Mistake #1: Using wait_trigger() for something that may already have happened

Wrong: a sequence calls reset_done.wait_trigger(), but it may start after reset has already finished
Right: reset_done.wait_on()

Why? As Case #2 shows, wait_trigger() only sees future triggers. wait_on() returns immediately if the event is already "on", so it works whether the waiter starts early or late.

Mistake #2: Expecting wait_ptrigger() to remember earlier time steps

wait_ptrigger() fixes same-time-step races (Case #6), but a trigger from an earlier time is still missed (Case #5). Choose wait_ptrigger() for "the next trigger, race-free" and wait_on() for "has it happened at all".

Mistake #3: Reusing an event with wait_on() without calling reset()

Wrong: waiting with wait_on() for the second reset of a test, using the same event as the first
Right: call reset() when the condition is no longer true, for example when reset is asserted again

Why? Once triggered, the event stays "on". Without reset(), every later wait_on() returns immediately, even though the second reset has not finished.

Practice Exercise

Challenge: A Reusable Reset-Done Event

A monitor watches an active-low reset vif.rstn. Write its run_phase so that a reset_done event is triggered each time reset deasserts, and turned off each time reset asserts. Then write a sequence body that waits correctly whether it starts before or after reset is done.

Hint

Trigger on the rising edge of rstn and call reset() on the falling edge. The sequence should use the method that checks the "on" state.

Solution

// In the monitor
uvm_event reset_done;                       // Created in build_phase: reset_done = new ("reset_done");

virtual task run_phase (uvm_phase phase);
  forever begin
    @(posedge vif.rstn);                    // Reset deasserted
    reset_done.trigger ();                  // Event turns "on"
    `uvm_info ("MON", "Reset done", UVM_LOW)

    @(negedge vif.rstn);                    // Reset asserted again
    reset_done.reset ();                    // Event turns "off" for the next cycle
  end
endtask

// In the sequence (reset_done handle obtained from the env or a uvm_event_pool)
virtual task body ();
  reset_done.wait_on ();                    // Returns at once if reset is already done
  // Start sending traffic here
endtask

Because the monitor turns the event off on every new reset, a sequence started during a later reset waits for that reset to finish, instead of returning immediately.

Summary

In this tutorial, you learned:

  • uvm_event extends uvm_event_base, which wraps a SystemVerilog event and adds state, waiter counts and callbacks
  • An event stays "on" after trigger() until reset() is called
  • wait_trigger() only sees future triggers and can race with a trigger in the same time step
  • wait_ptrigger() removes the same-time-step race but does not see triggers from earlier time steps
  • wait_on() returns immediately if the event is already on, so it suits "has this happened" conditions
  • A parameterized uvm_event #(T) can pass data with trigger() and get_trigger_data()
Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5
Cron Job Starts