How does UVM Comparator works?

How does a UVM Comparator works?
For example if I pass 4 transactions on a comparator’s before_export, let’s say tr0, tr1, tr2, and tr3, and then I pass 1 transaction to after_export. So to which expected transaction will the observe transaction be compared? Is it to tr0 or to tr3?

All of the comparators provided by UVM are in-order. The first transaction received on the before_export port will be compared to the first transaction received on the after_export port. If you want to do any out-of-order comparison, you will need to write the comparator yourself.

In reply to cgales:

Thanks for the info.
So it means that the in_order_comparator itself has a queue in it that compares the expected and the observe transactions in a first-in-first-out basis?

In reply to Reuben:

Yes, they have fifos that they store the transactions in. You can always look at the source code and see how it works.

In reply to cgales:

Thanks. I have another question. Let’s say 4 expected transactions came but only 3 transactions came, will the test still pass or will the comparator flag an error?

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;

  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);, 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;

  virtual function string get_type_name();
    return type_name;

  virtual function void connect_phase(uvm_phase phase);

  // 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;
    forever begin
      if(!comp_type::comp(b, a)) begin

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

        uvm_report_warning("Comparator Mismatch", s);


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

      // 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;

  // 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;

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.

In reply to tfitz:

Thanks for the info.
Regarding the fifo, I’m not sure what’s happening in my comparator’s fifo because it only compares the last expected transaction to all the observed transactions.

Here’s what’s happening:

I passed for addresses:

When the observed transactions came, here’s the report:

lhs = 'h2824 : rhs = 'h2821
lhs = 'h2824 : rhs = 'h2822
lhs = 'h2824 : rhs = 'h2823
lhs = 'h2824 : rhs = 'h2824

Only the last one matches. The frist 3 are miscompares.

This is how I passed the expected:

num = 0;
repeat(4) begin
  exp_tr.addr = 15'h2821 + (num*1);

exp_export is an analysis_export in the scoreboard.
I connect it with the before_export of the comparator:

// In build_phase of scoreboard, comp_cmd is the in_order_comparator
exp_export = comp_cmd.before_export;

In reply to Reuben:

The example you posted shows that you are re-using the same exp_tr handle. You need to make sure that you don’t re-use the same handles since the objects may be stored in a fifo and the comparison may occur at some later time. Use the clone() method or create new objects.

In reply to cgales:

Hi cgales,

I don’t understand exactly what you mean by “don’t re-use the same handles since the objects may be stored in a fifo and the comparison may occur at some later time”. Can you elaborate it?

This is how I understand what should happen inside the fifo:

fifo_slot_0: first exp_tr with the member addr equal to 'h2821
fifo_slot_1: second exp_tr with the member addr equal to 'h2822
fifo_slot_2: third exp_tr with the member addr equal to 'h2823
fifo_slot_3: fourth exp_tr with the member addr equal to 'h2824

So even though I’m using the same exp_tr handle, the value of the member addr should still be changing.

Creating a new object actually works but I just want to understand why it works. =)

In reply to Reuben:

Your loop uses the same exp_tr variable over and over, passing the same object handle to the comparitor fifo. When you change exp_tr.addr, you change it for all instances stored in the fifo. You need unique handles for each comparitor fifo item.

In reply to cgales:

Hi cgales,

Thank you.
For out of order transactions, do you have any suggestions on how to do it?

What I did is, I have an array in the scoreboard and then I store the data there using the addr as the index.

Do you have a better way of doing it?

In reply to Reuben:

Using an associative array might work, except for the case where you could have two expected elements with the same address. This would result in the first item being replaced by the second item.

I find that queues work well since it is fairly quick to add new items to the end of the queue and it maintains ordering. Searching through the queue might not be very efficient as the number of outstanding elements grows, but it works ok for a small number.

In reply to cgales:

Hi where we have to use clone method in the scoreboard where i am getting transactions or i have to send clone in the scoreboard!!

In reply to nocsmsourabh:

Copy-on-write. If you modify a transaction, you need to make a clone (clone is: create followed by copy)

In reply to dave_59: