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
:
- Supporting more functions, easier to control the event and also more readable. For example, given the event named
func_done
, trigger the event withfunc_done.trigger()
is much more clear than->func_done
. - Allowing passing data with the event.
- Having callback function, which will be run before and after the event is triggered.
- 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 objecttrigger()
: 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_pool
The uvm_event_pool
is a uvm_pool
for events.
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 inuvm_event_pool
with<string key>
. If no item exist by the given input string, a newuvm_event
object will be created for that key.
So to create/get an uvm_event which is shared globally, we just need to call:
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 thetrigger()
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)
.
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.
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:
Debug and Caveat
Debug uvm_event
To debug uvm_event
, first we need to make sure if we trigger and wait_trigger on the same uvm_event
object by checking the m_inst_id
of the uvm_event
object handle that we call these methods on (this m_inst_id
is the variable of uvm_object
class).
Then we could call the print()
function of the uvm_event
to check the event state, number of waiting trigger, etc.
However, to quick debug, I usually do as below:
Event wait deadlock
Example
Edaplayground
Finding more information
[Tags
uvm
]