How does UVM Comparator works?

In reply to Reuben:

As Chuck said, you can look at the source code. Here it is:

class uvm_in_order_comparator 
  #( type T = int ,
     type comp_type = uvm_built_in_comp #( T ) ,
     type convert = uvm_built_in_converter #( T ) , 
     type pair_type = uvm_built_in_pair #( T ) )
    extends uvm_component;

  typedef uvm_in_order_comparator #(T,comp_type,convert,pair_type) this_type;
  `uvm_component_param_utils(this_type)

  const static string type_name = 
    "uvm_in_order_comparator #(T,comp_type,convert,pair_type)";

  // Port: before_export
  //
  // The export to which one stream of data is written. The port must be
  // connected to an analysis port that will provide such data. 

  uvm_analysis_export #(T) before_export;


  // Port: after_export
  //
  // The export to which the other stream of data is written. The port must be
  // connected to an analysis port that will provide such data. 

  uvm_analysis_export #(T) after_export;


  // Port: pair_ap
  //
  // The comparator sends out pairs of transactions across this analysis port.
  // Both matched and unmatched pairs are published via a pair_type objects.
  // Any connected analysis export(s) will receive these transaction pairs.

  uvm_analysis_port   #(pair_type) pair_ap;
  
  local uvm_tlm_analysis_fifo #(T) m_before_fifo;
  local uvm_tlm_analysis_fifo #(T) m_after_fifo;

  int m_matches, m_mismatches;

  function new(string name, uvm_component parent);

    super.new(name, parent);

    before_export = new("before_export", this);
    after_export  = new("after_export", this);
    pair_ap       = new("pair_ap", this);

    m_before_fifo = new("before", this);
    m_after_fifo  = new("after", this);
    m_matches = 0;
    m_mismatches = 0;

  endfunction
  
  virtual function string get_type_name();
    return type_name;
  endfunction

  virtual function void connect_phase(uvm_phase phase);
    before_export.connect(m_before_fifo.analysis_export);
    after_export.connect(m_after_fifo.analysis_export);
  endfunction


  // Task- run_phase
  //
  // Internal method.
  //
  // Takes pairs of before and after transactions and compares them. 
  // Status information is updated according to the results of the comparison.
  // Each pair is published to the pair_ap analysis port.

  virtual task run_phase(uvm_phase phase);
 
    pair_type pair;
    T b;
    T a;
  
    string s;
    super.run_phase(phase); 
    forever begin
      
      m_before_fifo.get(b);
      m_after_fifo.get(a);
      
      if(!comp_type::comp(b, a)) begin

        $sformat(s, "%s differs from %s", convert::convert2string(a),
                                          convert::convert2string(b));

        uvm_report_warning("Comparator Mismatch", s);

        m_mismatches++;

      end
      else begin
        s = convert::convert2string(b);
        uvm_report_info("Comparator Match", s);
        m_matches++;
      end

      // we make the assumption here that a transaction "sent for
      // analysis" is safe from being edited by another process.
      // Hence, it is safe not to clone a and b.
      
      pair = new("after/before");
      pair.first = a;
      pair.second = b;
      pair_ap.write(pair);
    end
  
  endtask


  // Function: flush
  //
  // This method sets m_matches and m_mismatches back to zero. The
  // <uvm_tlm_fifo::flush> takes care of flushing the FIFOs.

  virtual function void flush();
    m_matches = 0;
    m_mismatches = 0;
  endfunction
  
endclass

You can see that the pair of get() calls at the beginning of the forever loop require that a matching number of transactions arrive on each export before the comparison happens. Therefore, if you get 4 of one and 3 of the other, you’ll be stuck waiting for the fourth transaction. In your test’s check_phase(), you can see if the fifos are empty. If not, then you have a mismatch in the number of transactions.