This is write-up about the internals of UVM 1800.2 analysis_port rabbit hole.

Starting with uvm_analysis_port.svh, where the doc has small snippet of uvm_analysis_port object.

//------------------------------------------------------------------------------
// Class -- NODOCS -- uvm_analysis_port
//
// Broadcasts a value to all subscribers implementing a <uvm_analysis_imp>.
// 
//| class mon extends uvm_component;
//|   uvm_analysis_port#(trans) ap;
//|
//|   function new(string name = "sb", uvm_component parent = null);
//|      super.new(name, parent);
//|      ap = new("ap", this);
//|   endfunction
//|
//|   task run_phase(uvm_phase phase);
//|       trans t;
//|       ...
//|       ap.write(t);
//|       ...
//|   endfunction
//| endclass

uvm_analysis_port is defined as

// @uvm-ieee 1800.2-2020 auto 12.2.10.1.1
class uvm_analysis_port # (type T = int)
  extends uvm_port_base # (uvm_tlm_if_base #(T,T));

and write calls tif.write() and tif is returned from get_if.

  // @uvm-ieee 1800.2-2020 auto 12.2.10.1.4
  function void write (input T t);
    uvm_tlm_if_base # (T, T) tif;
    for (int i = 0; i < this.size(); i++) begin
      tif = this.get_if (i);
      if ( tif == null )
        uvm_report_fatal ("NTCONN", {"No uvm_tlm interface is connected to ", get_full_name(), " for executing write()"}, UVM_NONE);
      tif.write (t);
    end 
  endfunction

uvm_tlm_if_base Link to heading

Let’s get uvm_tlm_if_base out of the way first, In uvm_tlm_ifs.svh, uvm_tlm_if_base is abstract class with port methods:

  • get
  • put
  • ..etc
// @uvm-ieee 1800.2-2020 auto 12.2.4.1
virtual class uvm_tlm_if_base #(type T1=int, type T2=int);

but they are all virtual and derived class must implement them.

  // @uvm-ieee 1800.2-2020 auto 12.2.4.2.3
  virtual task peek( output T2 t );
    uvm_report_error("peek", `UVM_TASK_ERROR, UVM_NONE);
  endtask

uvm_port_base Link to heading

The chunk of work is done in uvm_port_base defined in uvm_port_base.svh

// @uvm-ieee 1800.2-2020 auto 5.5.1
virtual class uvm_port_base #(type IF=uvm_void) extends IF;

uvm_port_base::get_if() is called in uvm_analysis_port::write(). which returns one port depending on the index.

  function uvm_port_base #(IF) get_if(int index=0);
    string s;
....
....
....
    foreach (m_imp_list[nm]) begin
      if (index == 0)
        return m_imp_list[nm];
      index--;
    end
  endfunction

m_imp_list is populated by m_add_list.

  local function void m_add_list           (this_type provider);
    ...
    ...
    for (int i = 0; i < provider.size(); i++) begin
      imp = provider.get_if(i);
      if (!m_imp_list.exists(imp.get_full_name()))
        m_imp_list[imp.get_full_name()] = imp; // <===================================
    end

  endfunction

m_add_list is called by resolve_bindings. resolves_bindings gets the port list from m_provided_by.

// This method is automatically called just before the start of the // end_of_elaboration phase. Users should not need to call it directly.

  virtual function void resolve_bindings();
    if (m_resolved) // don't repeat ourselves
     return;

    if (is_imp()) begin
      m_imp_list[get_full_name()] = this;
    end
    else begin
      foreach (m_provided_by[nm]) begin
        this_type port;
        port = m_provided_by[nm];
        port.resolve_bindings();
        m_add_list(port); // <===================================
      end
    end
  endfunction

And finally, m_provided_by should be populated by connect

  // @uvm-ieee 1800.2-2020 auto 5.5.2.14
  virtual function void connect (this_type provider);
  ...
  ...  =
    m_provided_by[provider.get_full_name()] = provider; // <===================================
    provider.m_provided_to[get_full_name()] = this;
    
  endfunction