Creating virtual function to get 'uvm_analysis_export' handles

I have following Systemverilog/UVM class structure. Is there a way to write a virtual function to get exp_axport and act_axport handles using sb_root handle?

virtual class sb_root extends uvm_scoreboard;
// common SB code here
endclass

virtual class sb_base #(type EXP_TXN_T = uvm_sequence_item,
	                    type ACT_TXN_T = uvm_sequence_item) extends sb_root;

	uvm_analysis_export #(EXP_TXN_T) exp_axport;
	uvm_analysis_export #(ACT_TXN_T) act_axport;
	
	virtual function void build_phase(uvm_phase phase);
		exp_axport = new("exp_axport", this);
		act_axport = new("act_axport", this);
	endfunction

	// other transaction type specific SB code here
endclass

class in_order_sb #(type EXP_TXN_T = uvm_sequence_item,
					type ACT_TXN_T = uvm_sequence_item
				   ) extends sb_base #(
				    .EXP_TXN_T(EXP_TXN_T),
					.ACT_TXN_T(ACT_TXN_T)
				   );
		// concrete SB code here		   
endclass

I’d like to create an associative array of scoreboards that stores all concrete in_order_sb handles with different parameter values.
e.g.

sb_root sb_map[string];
sb_map["AXI"] = axi_in_order_sb; // where axi_in_order_sb is defined as in_order_sb #(axi_item, axi_item)
sb_map["APB"] = apb_in_order_sb; // where apb_in_order_sb is defined as in_order_sb #(apb_item, apb_item)

If I can access uvm_analysis_exports using sb_root handle, then I can iterate through the sb_map and connect ports.

You can’t directly access the export handles from sb_root; you have to downcast them to the specialized parameterized version of sb_base. The code that does this would need to be aware of all the possible specializations of sb_base.

class test extends uvm_test;
  typedef sb_base #(axi_item, axi_item) axi_sb_base;
  typedef sb_base #(axi_item, axi_item) apb_sb_base;
  
  axi_sb_base axi_sb;
  apb_sb_base apb_sb;
  sb_root sb_map[string];

  `uvm_component_utils(test)
  function new(string name, uvm_component parent);
    super.new(name,parent);
  endfunction
  function void build_phase(uvm_phase phase);
    sb_root handle;
    axi_sb_base axi_sb_h;
    apb_sb_base apb_sb_h;
    axi_sb = new("axi_sb",this);
    apb_sb = new("apb_sb",this);
    
    sb_map["AXI"] = axi_sb; 
    sb_map["APB"] = apb_sb;
    
    foreach(sb_map[T]) begin : sb_map_loop
      handle = sb_map[T];
      case(handle.get_object_type())
        axi_sb_base::get_type() : begin
          assert($cast(axi_sb_h,handle));
          // connect matching monitor
        end
        apb_sb_base::get_type() : begin
          assert($cast(apb_sb_h,handle));
          // connect matching monitor
        end
      // ...
      default: `uvm_fatal("NO_MATCH","No matching export handle")
    endcase
        end : sb_map_loop
    // ...
  endfunction
endclass

Another option is using a common transaction base class and use that to parameterize your scoreboard connections. Then downcast them when you pull them out of your scoreboard write methods.

That works. Thank you!