How to use uvm_event and uvm_event_pool

Systemverilog already has event, used for communication and synchronization. However if using uvm, my opinion is we should use uvm_event instead. It’s a class, so we can easily to manage the event in oop style. And thanks to that, uvm_event_pool is created to support us.


uvm_event vs Systemverilog event

My opinion is, always use the uvm_event, because it has many advantages over the Systemverilog event:

  1. Supporting more functions, easier to control the event and also more readable. For example, given the event named func_done, trigger the event with func_done.trigger() is much more clear than ->func_done.
  2. Allowing passing data with the event.
  3. Having callback function, which will be run before and after the event is triggered.
  4. Having uvm_event_pool, which is very convenience in sharing the event between components in the environment.

How to use uvm_event and uvm_event_pool

uvm_event class basic function

The uvm_event#(T) class supports several basic methods as below:

  • new(): Creates a new event object
  • trigger(): Trigger the event.
  • wait_trigger(): Waits for the next trigger of the event.
  • wait_ptrigger(): Waits for a persistent trigger of the event, avoids the event race conditions.
  • is_on(): Returns 1 if the event has triggered.
  • is_off(): Returns 1 if the event has not been triggered since created or reset.
  • wait_on(): Waits for the event to be triggered for the first time, returns if the event is already triggered.
  • wait_off(): When the event is triggered and “on”, this method wait for the event to be “off” by reset method.
  • reset(): Resets the event, turns its state to “off”.
  • get_trigger_time(): Returns the time when the event was last triggered.
  • get_num_waiters(): Returns the number of processes currently waiting on the event.

Event race condition and persistent trigger

When the trigger() method and the wait_trigger() method are called at the same simulation time, we call it event race condition.

When this happens, we will not know if the trigger() is occurred before or after the wait_trigger(). If the trigger() is executed first, we end up in a deadlock situation. The reason is the wait_trigger() will wait for the next trigger of the event, but it the trigger() is already processed prior to the wait.

To avoid this race condition, we can use the wait_ptrigger() instead of wait_trigger(). This function will consider the trigger event as persistent for certain amount of time. Therefore when the race condition as above happens, this wait_ptrigger() will still recognize the trigger event, and then return immediately as expected.

We can also avoid race condition by having a coding convention as below:

uvm_event m_my_event = new();

//for trigger,
//only trigger when the state of event is off (by reset or the event has not been trigger at all)
m_my_event.wait_off();
m_my_event.trigger();

//for wait_trigger, reset the event to off state after wait_trigger() return
m_my_event.wait_trigger();
m_my_event.reset();

uvm_event_pool

The uvm_event_pool is a uvm_pool for events.

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

We’ll go in detail of uvm_pool in another post. In this post, let’s think of it as a global associative array where the keys are strings of event names, and the values are the uvm_event objects.

Also, uvm_pool is a singleton class , that explains why it has global access.

uvm_event_pool support several methods, but the most commonly used is get_global().

  • get_global(<string key>): Return the uvm event object that stored in uvm_event_pool with <string key>. If no item exist by the given input string, a new uvm_event object will be created for that key.

So to create/get an uvm_event which is shared globally, we just need to call:

   // if there is no uvm_event stored under name "test_finish_ev",
   // a new uvm_event object will be created by the pool
   uvm_event m_finish_ev = uvm_event_pool::get_global("test_finish_ev");
   m_finish_ev.wait_trigger();

   // We can directly use the event as below,
   // no need to care if the event is created or not, since the pool will handle that

   // trigger an event
   uvm_event_pool::get_global("test_finish_ev").trigger();
   ...
   // wait for an event 
   uvm_event_pool::get_global("test_finish_ev").wait_trigger();

uvm_event with passing data

As we see the uvm_event class has parameter T uvm_event#(type T=uvm_object). This allow us to attach data with the event.

Note that the parameter T is only supported in uvm1.2 version. In uvm1.1d, the attached data of the event trigger must be an uvm_object.

We have several supported methods for to attach data to event below:

  • trigger(T data=null): Trigger the event. If there is data passing along with the event, put the data type T in the argument.
  • get_trigger_data(): Return the data type T which is provided in the trigger() function.
  • wait_trigger_data(output T data): wait_trigger() then get_trigger_data().
  • wait_ptrigger_data(output T data): wait_ptrigger() then get_trigger_data().

uvm_event_pool with passing data

As discussed above, the uvm_event_pool is just a uvm_pool of uvm_event#(uvm_object).

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

This means that if using the uvm_event_pool, we can only passing data with type uvm_object or any of it’s child class. To have a pool which allows passing other data type, we must define a pool by ourselves, just as uvm_event_pool defined by uvm1.2

For example, an uvm_event_int_pool allows passing data type int along with the trigger event.

typedef uvm_object_string_pool #(uvm_event#(int)) uvm_event_int_pool;
...
  uvm_event_int_pool::get_global("get_data_ev").trigger(279);
...
  int m_data;
  uvm_event_int_pool::get_global("get_data_ev").wait_trigger();

  //--> m_data be 279
  m_data = uvm_event_int_pool::get_global("get_data_ev").get_trigger_data();

uvm_event callbacks

The callbacks methods are used pretty common in uvm methodology. In this case, we will provide the functions that will be call by uvm_event just before and after the event is triggered.

To do this, we’ll add the uvm_event_callback object to the event. In this object, we will implement 2 pre_trigger() and post_trigger(). These are 2 methods that will be call by uvm_event before and after trigger, respectively.

Let’s look at the example below:

  // create callback class
  class aes_seq_item_event_callback extends uvm_event_callback;
     virtual function bit pre_trigger(uvm_event#(T) e, T data);
        if (data != null ) data.print();
     endfunction
  endclass

  aes_seq_item_event_callback m_aes_cback = new();

  uvm_event_pool::get_global("get_seq_item_ev").add_callback(m_aes_cback); //add callback obj
  ...
  aes_seq_item m_aes_item = aes_seq_item::type_id::create("aes_seq_item");

  //--> the pre_trigger function will call m_aes_item.print() before trigger the event.
  uvm_event_pool::get_global("get_seq_item_ev").trigger(m_aes_item);

Example

Edaplayground


Finding more information

  1. uvm_event.svh
  2. uvm_event_callback.svh
  3. uvm_pool.svh


[Tags uvm  ]