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