UVM register model defines several callback hooks. This is deepdive into how it works.
pre_read example Link to heading
First, we need to define callback class extending uvm_reg_cbs. I am using pre_read in this case.
class mycb extends uvm_reg_cbs;
`uvm_object_utils(mycb)
function new(string name="");
super.new(name);
endfunction
virtual task pre_read(uvm_reg_item rw);
$display("cb called");
endtask
endclass
Then register the callback with required uvm register(not confusing at all!).
mycb m_mycb;
virtual function void build_phase(uvm_phase phase);
...
m_mycb = mycb::type_id::create("m_mycb");
endfunction
virtual function void connect_phase(uvm_phase phase);
,,,
uvm_callbacks #(uvm_reg,mycb)::add( m_myblock_ral.m_myreg,m_mycb);
endfunction
Calling callback Link to heading
So, what happens when we call m_myreg.read()?
read is defined in uvm_reg.svh which calls XreadX
task uvm_reg::read(output uvm_status_e status,
output uvm_reg_data_t value,
input uvm_path_e path = UVM_DEFAULT_PATH,
input uvm_reg_map map = null,
input uvm_sequence_base parent = null,
input int prior = -1,
input uvm_object extension = null,
input string fname = "",
input int lineno = 0);
XatomicX(1);
XreadX(status, value, path, map, parent, prior, extension, fname, lineno);
XatomicX(0);
endtask: read
XreadX calls do_read
do_read(rw);
status = rw.status;
value = rw.value[0];
endtask: XreadX
Voila! do_read loops over callbacks from cbs and calls pre_read
for (uvm_reg_cbs cb=cbs.first(); cb!=null; cb=cbs.next())
cb.pre_read(rw);
Registering callback Link to heading
uvm_reg callback registration Link to heading
Now, we know where pre_read is called, who populates cbs?
from uvm_reg.svh
uvm_reg_cb_iter cbs = new(this)
And
typedef uvm_callback_iter#(uvm_reg, uvm_reg_cbs) uvm_reg_cb_iter;
And uvm_callback_iter is defined as an iterator wrapper for uvm_callbacks
function CB first();
m_cb = uvm_callbacks#(T,CB)::get_first(m_i, m_obj);
return m_cb;
endfunction
Weird! Let’s circle back later.
For now, let’s look at uvm_reg.svh, where uvm_register_cb macro is used
`uvm_register_cb(uvm_reg, uvm_reg_cbs)
Which leads to generic UVM callback infrastructure.In uvm_callback_defines.svh,
`define uvm_register_cb(T,CB) \
static local bit m_register_cb_``CB = uvm_callbacks#(T,CB)::m_register_pair(`"T`",`"CB`");
Which expands to
static local bit m_register_cb_uvm_reg_cbs = uvm_callbacks#(uvm_reg,uvm_reg_cbs)::m_register_pair("uvm_reg","uvm_reg_cbs");
m_register_pair is static function that sets m_registered to 1 and stored strings for both classes
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
UVM generic callback infrastructure Link to heading
At this point, we covered things uvm_reg does to register callback. But How does this work with ::add?
uvm_callbacks #(uvm_reg,mycb)::add( m_myblock_ral.m_myreg,m_mycb);
Looking at uvm_callback.svh, add is static and there is a different one for each of uvm_callback specializations.
static function void add(T obj, uvm_callback cb, uvm_apprepend ordering=UVM_APPEND);
First it checks that object and cb are registered
if (!m_base_inst.check_registration(obj,cb)) begin
check_registration should return 1 as uvm_reg used uvm_register_cb to register the callback.
function bit check_registration(uvm_object obj, uvm_callback cb);
this_type st, dt;
if (m_is_registered(obj,cb))
return 1;
// Need to look at all possible T/CB pairs of this type
foreach(m_this_type[i])
if(m_b_inst != m_this_type[i] && m_this_type[i].m_is_registered(obj,cb))
return 1;
if(obj == null) begin
foreach(m_derived_types[i]) begin
dt = uvm_typeid_base::typeid_map[m_derived_types[i] ];
if(dt != null && dt.check_registration(null,cb))
return 1;
end
end
return 0;
Then, It looks up the callbacks for that type(uvm_reg)
q = m_base_inst.m_pool.get(obj);
if (q==null) begin
q=new;
m_base_inst.m_pool.add(obj,q);
end
Then it pushes that cb to the queue, if not registered already
void'(m_cb_find_name(q, cb.get_name(), {"object instance ", obj.get_full_name()}));
if(ordering == UVM_APPEND)
q.push_back(cb);
else
q.push_front(cb);
end
Wait! where is cbs populated and what is uvm_callbacks?
Actually, The queue q is coming from m_base_inst
q = m_base_inst.m_pool.get(obj);
which is of type uvm_callbacks#(T,uvm_callback)
static uvm_callbacks#(T,uvm_callback) m_base_inst;
To sum up, q is inside uvm_callbacks. uvm_callbacks_iterator works as wrapper for uvm_callbacks. cbs type is uvm_callback_iterator