This is a deepdive how UVM implements callbacks. It’s useful to notifiy user code with specific conditions deep inside a UVC.
Callback use-model Link to heading
Starting with the callback definition where it extends uvm_callback
and define callback task.
class my_callback extends uvm_callback;
virtual task my_callback_task(); endtask
endclass
Then instrument the code with calls to uvm_do_callbacks
class my_uvm_component;
`uvm_register_cb(my_uvm_component,my_callback)
task run_phase(uvm_phase phase);
...
`uvm_do_callbacks(my_uvm_component,my_callback,my_callback_task());
...
endtask
encclass
Finally use code can extends the callback defined above my_callback
and implement the callback task my_callback_task
class some_random_user_code extends my_callback;
task my_callback_task;
`uvm_info("Yo sup from my_callback_task",UVM_LOW);
endtask
endclass
and finally user callback is registered
some_random_user_code cb;
cb = some_random_user_code::type_id::create("cb1", this);
uvm_callbacks#(my_uvm_component,my_callback)::add(<INST>,cb);
So, the 2 macros are used to implement the callbacks. Let’s dig deeper.
uvm_register_cb Link to heading
uvm_register_cb
is defined in macros/uvm_callback_defines.svh
. that expands to a call to m_register_pair
`define uvm_register_cb(T,CB) \
static local bit m_register_cb_``CB = uvm_callbacks#(T,CB)::m_register_pair(`"T`",`"CB`");
m_register_pair
links the cb to class type passed to tname
. Note that it calls get()
which returns the singleton of uvm_callbacks
class as shown below.
static function bit m_register_pair(string tname="", cbname="");
this_type inst = get();
m_typename = tname;
super_type::m_typename = tname;
m_typeid.typename = tname;
m_cb_typename = cbname;
m_cb_typeid.typename = cbname;
inst.m_registered = 1;
return 1;
endfunction
static function this_type get();
m_user_inst = this_user_type::get();
m_super_inst = this_super_type::get();
m_s_typeid = uvm_typeid#(ST)::get();
if(m_d_inst == null) begin
m_d_inst = new;
end
return m_d_inst;
endfunction
uvm_do_callbacks Link to heading
As documentation shows, uvm_do_callbacks
is called to execute the registered callbacks.
//-----------------------------------------------------------------------------
// MACRO -- NODOCS -- `uvm_do_callbacks
//
//| `uvm_do_callbacks(T,CB,METHOD)
//
// Calls the given ~METHOD~ of all callbacks of type ~CB~ registered with
// the calling object (i.e. ~this~ object), which is or is based on type ~T~.
//
// This macro executes all of the callbacks associated with the calling
// object (i.e. ~this~ object). The macro takes three arguments:
//
// - CB is the class type of the callback objects to execute. The class
// type must have a function signature that matches the METHOD argument.
//
// - T is the type associated with the callback. Typically, an instance
// of type T is passed as one the arguments in the ~METHOD~ call.
//
// - METHOD is the method call to invoke, with all required arguments as
// if they were invoked directly.
//
// For example, given the following callback class definition:
//
//| virtual class mycb extends uvm_cb;
//| pure function void my_function (mycomp comp, int addr, int data);
//| endclass
//
// A component would invoke the macro as
//
//| task mycomp::run_phase(uvm_phase phase);
//| int curr_addr, curr_data;
//| ...
//| `uvm_do_callbacks(mycb, mycomp, my_function(this, curr_addr, curr_data))
//| ...
//| endtask
//-----------------------------------------------------------------------------
// @uvm-ieee 1800.2-2020 auto B.4.3
`define uvm_do_callbacks(T,CB,METHOD) \
`uvm_do_obj_callbacks(T,CB,this,METHOD)
Looking deeper into uvm_do_obj_callbacks
, which uses uvm_callback_iter
iter to loop over registered callbacks and executes them with cb.METHOD
.
// @uvm-ieee 1800.2-2020 auto B.4.4
`define uvm_do_obj_callbacks(T,CB,OBJ,METHOD) \
begin \
uvm_callback_iter#(T,CB) iter = new(OBJ); \
CB cb = iter.first(); \
while(cb != null) begin \
`uvm_cb_trace_noobj(cb,$sformatf(`"Executing callback method 'METHOD' for callback %s (CB) from %s (T)`",cb.get_name(), OBJ.get_full_name())) \
cb.METHOD; \
cb = iter.next(); \
end \
end
uvm_callback_iter
is just wrapper on top methods defined in uvm_callback
such as get_first
// @uvm-ieee 1800.2-2020 auto D.1.1
class uvm_callback_iter#(type T = uvm_object, type CB = uvm_callback) extends uvm_void;
local int m_i;
local T m_obj;
local CB m_cb;
// Function -- NODOCS -- new
//
// Creates a new callback iterator object. It is required that the object
// context be provided.
// @uvm-ieee 1800.2-2020 auto D.1.2.1
function new(T obj);
m_obj = obj;
endfunction
// Function -- NODOCS -- first
//
// Returns the first valid (enabled) callback of the callback type (or
// a derivative) that is in the queue of the context object. If the
// queue is empty then ~null~ is returned.
// @uvm-ieee 1800.2-2020 auto D.1.2.2
function CB first();
m_cb = uvm_callbacks#(T,CB)::get_first(m_i, m_obj);
return m_cb;
endfunction
get_first
calls m_get_q
and loops over it to return cb.
// Function -- NODOCS -- get_first
//
// Returns the first enabled callback of type CB which resides in the queue for ~obj~.
// If ~obj~ is ~null~ then the typewide queue for T is searched. ~itr~ is the iterator;
// it will be updated with a value that can be supplied to <get_next> to get the next
// callback object.
//
// If the queue is empty then ~null~ is returned.
//
// The iterator class <uvm_callback_iter> may be used as an alternative, simplified,
// iterator interface.
// @uvm-ieee 1800.2-2020 auto 10.7.2.4.1
static function CB get_first (ref int itr, input T obj);
uvm_queue#(uvm_callback) q;
CB cb;
void'(get());
m_get_q(q,obj);
for(itr = 0; itr<q.size(); ++itr) begin
if($cast(cb, q.get(itr)) && cb.callback_mode()) begin
return cb;
end
end
return null;
endfunction
add Link to heading
This is probably the most important part of callback mechanism, but it is simple really. The callback is added to q based on order(UVM_APPEND).
// Group -- NODOCS -- Add/delete interface
// Function -- NODOCS -- add
//
// Registers the given callback object, ~cb~, with the given
// ~obj~ handle. The ~obj~ handle can be ~null~, which allows
// registration of callbacks without an object context. If
// ~ordering~ is UVM_APPEND (default), the callback will be executed
// after previously added callbacks, else the callback
// will be executed ahead of previously added callbacks. The ~cb~
// is the callback handle; it must be non-~null~, and if the callback
// has already been added to the object instance then a warning is
// issued. Note that the CB parameter is optional. For example, the
// following are equivalent:
//
//| uvm_callbacks#(my_comp)::add(comp_a, cb);
//| uvm_callbacks#(my_comp, my_callback)::add(comp_a,cb);
// @uvm-ieee 1800.2-2020 auto 10.7.2.3.1
static function void add(T obj, uvm_callback cb, uvm_apprepend ordering=UVM_APPEND);
uvm_queue#(uvm_callback) q;
...
...
//check if already exists in the queue
if(m_cb_find(q,cb) != -1) begin
uvm_report_warning("CBPREG", { "Callback object ", cb.get_name(), " is already registered",
" with object ", obj.get_full_name() }, UVM_NONE);
end
else begin
if(ordering == UVM_APPEND) begin
q.push_back(cb);
end
else begin
q.push_front(cb);
end
end
end
endfunction