UVM object compare is done with the compare() method that every class derived from uvm_object inherits. It checks the fields of two objects and returns 1 when they match and 0 when they don't. The fields it checks come from do_compare(), which you either write yourself or let the UVM field macros generate.

Comparing objects is the core of most checking in a testbench. A scoreboard receives an actual transaction from a monitor, builds or looks up the expected one, and calls compare() to decide whether the DUT behaved correctly.

8 min read | Intermediate Level

What You'll Learn

  • Use compare() to check two objects field by field
  • Read the miscompare messages produced by field-macro comparisons
  • Write a do_compare() method by hand
  • Use uvm_comparer calls so a hand-written compare still reports what failed

Syntax


function bit compare (uvm_object rhs, uvm_comparer comparer = null);  // 1 = match, 0 = miscompare
virtual function bit do_compare (uvm_object rhs, uvm_comparer comparer); // hook you override
  • rhs is the object to compare against. The object you call the method on is the left-hand side, so obj2.compare(obj1) treats obj2 as lhs and obj1 as rhs.
  • comparer is an optional policy object. When you leave it out, the global uvm_default_comparer is used.
  • compare() is not virtual and should not be overridden. To include a class's own fields, override do_compare() instead.

How a Comparison Runs

compare() sets up the comparer, then calls do_compare(). Inside do_compare(), each field is checked, and the comparer keeps count of the miscompares and prints messages about them.

The comparer is what makes a compare useful for debugging. A bare 0 tells you only that something differs, while the comparer's messages tell you which field differed and what the two values were.

Comparing with Field Macros

When every field is registered with a field macro, do_compare() is generated for you. Here Packet holds one field and Object holds an enum, an integral field, a string and a nested Packet.


typedef enum {FALSE, TRUE} e_bool;

class Packet extends uvm_object;
  rand bit[15:0] 	m_addr;

  `uvm_object_utils_begin(Packet)
  	`uvm_field_int(m_addr, UVM_DEFAULT)
  `uvm_object_utils_end

  function new(string name = "Packet");
    super.new(name);
  endfunction
endclass

class Object extends uvm_object;
  rand e_bool 				m_bool;
  rand bit[3:0] 			m_mode;
  string 					m_name;
  rand Packet 				m_pkt;

  function new(string name = "Object");
    super.new(name);
    m_name = name;
    m_pkt = Packet::type_id::create("m_pkt");
    m_pkt.randomize();
  endfunction

  `uvm_object_utils_begin(Object)
  	`uvm_field_enum(e_bool, m_bool, UVM_DEFAULT)
  	`uvm_field_int (m_mode, 		UVM_DEFAULT)
  	`uvm_field_string(m_name, 		UVM_DEFAULT)
  	`uvm_field_object(m_pkt, 		UVM_DEFAULT)
  `uvm_object_utils_end
endclass

The test randomizes two objects, compares them, then copies one field at a time from obj1 into obj2 and compares again after each step.


class base_test extends uvm_test;
  `uvm_component_utils(base_test)
  function new(string name = "base_test", uvm_component parent=null);
    super.new(name, parent);
  endfunction

  function void build_phase(uvm_phase phase);
    // Create two objects, randomize them and print the contents
    Object obj1 = Object::type_id::create("obj1");
    Object obj2 = Object::type_id::create("obj2");
    obj1.randomize();
    obj1.print();
    obj2.randomize();
    obj2.print();

    if (obj2.compare(obj1))
      `uvm_info("TEST", "obj1 and obj2 are same", UVM_LOW)
    else
      `uvm_info("TEST", "obj1 and obj2 are different", UVM_LOW)

    `uvm_info("TEST", "Copy m_bool", UVM_LOW)
    obj2.m_bool = obj1.m_bool;
    if (obj2.compare(obj1))
      `uvm_info("TEST", "obj1 and obj2 are same", UVM_LOW)
    else
      `uvm_info("TEST", "obj1 and obj2 are different", UVM_LOW)

    `uvm_info("TEST", "Copy m_mode", UVM_LOW)
    obj2.m_mode = obj1.m_mode;
    if (obj2.compare(obj1))
      `uvm_info("TEST", "obj1 and obj2 are same", UVM_LOW)
    else
      `uvm_info("TEST", "obj1 and obj2 are different", UVM_LOW)

    `uvm_info("TEST", "Copy m_name", UVM_LOW)
    obj2.m_name = obj1.m_name;
    if (obj2.compare(obj1))
      `uvm_info("TEST", "obj1 and obj2 are same", UVM_LOW)
    else
      `uvm_info("TEST", "obj1 and obj2 are different", UVM_LOW)

    `uvm_info("TEST", "Copy m_pkt.m_addr", UVM_LOW)
    obj2.m_pkt.m_addr = obj1.m_pkt.m_addr;
    if (obj2.compare(obj1))
      `uvm_info("TEST", "obj1 and obj2 are same", UVM_LOW)
    else
      `uvm_info("TEST", "obj1 and obj2 are different", UVM_LOW)
  endfunction
endclass

module tb;
	initial begin
		run_test("base_test");
	end
endmodule
Output
UVM_INFO @ 0: reporter [RNTST] Running test base_test...
----------------------------------
Name        Type      Size  Value
----------------------------------
obj1        Object    -     @1903
  m_bool    e_bool    32    TRUE
  m_mode    integral  4     'h9
  m_name    string    4     obj1
  m_pkt     Packet    -     @1905
    m_addr  integral  16    'h3cb6
----------------------------------
----------------------------------
Name        Type      Size  Value
----------------------------------
obj2        Object    -     @1907
  m_bool    e_bool    32    FALSE
  m_mode    integral  4     'hf
  m_name    string    4     obj2
  m_pkt     Packet    -     @1908
    m_addr  integral  16    'h64c1
----------------------------------
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_comparer.svh(351) @ 0: reporter [MISCMP] Miscompare for obj2.m_bool: lhs = FALSE : rhs = TRUE
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_comparer.svh(382) @ 0: reporter [MISCMP] 1 Miscompare(s) for object obj1@1903 vs. obj2@1907
UVM_INFO testbench.sv(71) @ 0: uvm_test_top [TEST] obj1 and obj2 are different
UVM_INFO testbench.sv(73) @ 0: uvm_test_top [TEST] Copy m_bool
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_comparer.svh(351) @ 0: reporter [MISCMP] Miscompare for obj2.m_mode: lhs = 'hf : rhs = 'h9
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_comparer.svh(382) @ 0: reporter [MISCMP] 1 Miscompare(s) for object obj1@1903 vs. obj2@1907
UVM_INFO testbench.sv(78) @ 0: uvm_test_top [TEST] obj1 and obj2 are different
UVM_INFO testbench.sv(80) @ 0: uvm_test_top [TEST] Copy m_mode
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_comparer.svh(351) @ 0: reporter [MISCMP] Miscompare for obj2.m_name: lhs = "obj2" : rhs = "obj1"
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_comparer.svh(382) @ 0: reporter [MISCMP] 1 Miscompare(s) for object obj1@1903 vs. obj2@1907
UVM_INFO testbench.sv(85) @ 0: uvm_test_top [TEST] obj1 and obj2 are different
UVM_INFO testbench.sv(87) @ 0: uvm_test_top [TEST] Copy m_name
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_comparer.svh(351) @ 0: reporter [MISCMP] Miscompare for obj2.m_pkt.m_addr: lhs = 'h64c1 : rhs = 'h3cb6
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_comparer.svh(382) @ 0: reporter [MISCMP] 1 Miscompare(s) for object obj1@1903 vs. obj2@1907
UVM_INFO testbench.sv(92) @ 0: uvm_test_top [TEST] obj1 and obj2 are different
UVM_INFO testbench.sv(94) @ 0: uvm_test_top [TEST] Copy m_pkt.m_addr
UVM_INFO testbench.sv(97) @ 0: uvm_test_top [TEST] obj1 and obj2 are same
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_report_server.svh(847) @ 0: reporter [UVM/REPORT/SERVER]

Each MISCMP message names the field with its full path, such as obj2.m_pkt.m_addr, and gives the lhs and rhs values. The nested Packet is compared field by field too, because `uvm_field_object performs a deep compare by default.

Notice that each call reports exactly one miscompare, even though four fields differ at the start. The comparer's show_max setting defaults to 1, and the macro-generated compare stops once that many miscompares have been found. That is why the fields appear one at a time as each is fixed.

To see every differing field in one call, pass your own comparer with a larger limit: create a uvm_comparer, set its show_max field, and call obj2.compare(obj1, my_comparer). Setting comparer options is covered in Setting Policy Using uvm_comparer.

Writing do_compare() by Hand

Without field macros, you override do_compare() and check each field yourself. Here only the plain `uvm_object_utils macro is used for factory registration, and convert2string() provides readable output since no fields are registered for print().


typedef enum {FALSE, TRUE} e_bool;

class Packet extends uvm_object;
  rand bit[15:0] 	m_addr;

  virtual function string convert2string();
    string contents;
    contents = $sformatf("m_addr=0x%0h", m_addr);
    return contents;
  endfunction

  `uvm_object_utils(Packet)
  virtual function bit do_compare(uvm_object rhs, uvm_comparer comparer);
    bit res;
    Packet _pkt;

    $cast(_pkt, rhs);
    // Include any fields from the parent class, then this class's own field

    res = 	super.do_compare(_pkt, comparer) &
			m_addr == _pkt.m_addr;
    `uvm_info(get_name(), $sformatf("In Packet::do_compare(), res=%0b", res), UVM_LOW)
    return res;
  endfunction

  function new(string name = "Packet");
    super.new(name);
  endfunction
endclass

class Object extends uvm_object;
  rand e_bool 				m_bool;
  rand bit[3:0] 			m_mode;
  string 					m_name;
  rand Packet 				m_pkt;

  function new(string name = "Object");
    super.new(name);
    m_name = name;
    m_pkt = Packet::type_id::create("m_pkt");
    m_pkt.randomize();
  endfunction

  // This function is used to print contents
  virtual function string convert2string();
    string contents = "";
    $sformat(contents, "%s m_name=%s", contents, m_name);
    $sformat(contents, "%s m_bool=%s", contents, m_bool.name());
    $sformat(contents, "%s m_mode=0x%0h", contents, m_mode);
    $sformat(contents, "%s %s", contents, m_pkt.convert2string());
    return contents;
  endfunction

  `uvm_object_utils(Object)

  // "rhs" does not contain m_bool, m_mode, etc since its a parent
  // handle. So cast into child data type and access using child handle
  // Compare each field from the casted handle with the local variables
  virtual function bit do_compare(uvm_object rhs, uvm_comparer comparer);
    bit res;
    Object _obj;
    $cast(_obj, rhs);
    res = 	super.do_compare(_obj, comparer) &
    		m_name == _obj.m_name &
    		m_mode == _obj.m_mode &
    		m_bool == _obj.m_bool &
    		m_pkt.do_compare(_obj.m_pkt, comparer);

    `uvm_info(get_name(), $sformatf("In Object::do_compare(), res=%0b", res), UVM_LOW)
    return res;
  endfunction
endclass

The pattern mirrors do_copy(): cast rhs to your own type, include super.do_compare() so parent fields are checked, then combine the result of every field check into one bit.

The test is the same idea as before, but it prints with convert2string() and uses a helper function for the comparison.


class base_test extends uvm_test;
  `uvm_component_utils(base_test)
  function new(string name = "base_test", uvm_component parent=null);
    super.new(name, parent);
  endfunction

  function void build_phase(uvm_phase phase);
    Object obj1 = Object::type_id::create("obj1");
    Object obj2 = Object::type_id::create("obj2");
    obj1.randomize();
    `uvm_info("TEST", $sformatf("Obj1.print: %s", obj1.convert2string()), UVM_LOW)
    obj2.randomize();
    `uvm_info("TEST", $sformatf("Obj2.print: %s", obj2.convert2string()), UVM_LOW)

    _compare(obj1, obj2);

      `uvm_info("TEST", "Copy m_bool", UVM_LOW)
    obj2.m_bool = obj1.m_bool;
        `uvm_info("TEST", $sformatf("Obj2.print: %s", obj2.convert2string()), UVM_LOW)
    _compare(obj1, obj2);

      `uvm_info("TEST", "Copy m_mode", UVM_LOW)
    obj2.m_mode = obj1.m_mode;
        `uvm_info("TEST", $sformatf("Obj2.print: %s", obj2.convert2string()), UVM_LOW)
    _compare(obj1, obj2);

      `uvm_info("TEST", "Copy m_name", UVM_LOW)
    obj2.m_name = obj1.m_name;
        `uvm_info("TEST", $sformatf("Obj2.print: %s", obj2.convert2string()), UVM_LOW)
    _compare(obj1, obj2);

      `uvm_info("TEST", "Copy m_pkt.m_addr", UVM_LOW)
    obj2.m_pkt.m_addr = obj1.m_pkt.m_addr;
    	`uvm_info("TEST", $sformatf("Obj2.print: %s", obj2.convert2string()), UVM_LOW)
    _compare(obj1, obj2);

  endfunction

  function void _compare(Object obj1, obj2);
    if (obj2.compare(obj1))
      `uvm_info("TEST", "obj1 and obj2 are same", UVM_LOW)
    else
      `uvm_info("TEST", "obj1 and obj2 are different", UVM_LOW)
  endfunction
endclass

module tb;
	initial begin
		run_test("base_test");
	end
endmodule
Output
UVM_INFO @ 0: reporter [RNTST] Running test base_test...
UVM_INFO testbench.sv(96) @ 0: uvm_test_top [TEST] Obj1.print:  m_name=obj1 m_bool=TRUE m_mode=0x9 m_addr=0x3cb6
UVM_INFO testbench.sv(98) @ 0: uvm_test_top [TEST] Obj2.print:  m_name=obj2 m_bool=FALSE m_mode=0xf m_addr=0x64c1
UVM_INFO testbench.sv(30) @ 0: reporter [m_pkt] In Packet::do_compare(), res=0
UVM_INFO testbench.sv(77) @ 0: reporter [obj2] In Object::do_compare(), res=0
UVM_INFO testbench.sv(128) @ 0: uvm_test_top [TEST] obj1 and obj2 are different
UVM_INFO testbench.sv(102) @ 0: uvm_test_top [TEST] Copy m_bool
UVM_INFO testbench.sv(104) @ 0: uvm_test_top [TEST] Obj2.print:  m_name=obj2 m_bool=TRUE m_mode=0xf m_addr=0x64c1
UVM_INFO testbench.sv(30) @ 0: reporter [m_pkt] In Packet::do_compare(), res=0
UVM_INFO testbench.sv(77) @ 0: reporter [obj2] In Object::do_compare(), res=0
UVM_INFO testbench.sv(128) @ 0: uvm_test_top [TEST] obj1 and obj2 are different
UVM_INFO testbench.sv(107) @ 0: uvm_test_top [TEST] Copy m_mode
UVM_INFO testbench.sv(109) @ 0: uvm_test_top [TEST] Obj2.print:  m_name=obj2 m_bool=TRUE m_mode=0x9 m_addr=0x64c1
UVM_INFO testbench.sv(30) @ 0: reporter [m_pkt] In Packet::do_compare(), res=0
UVM_INFO testbench.sv(77) @ 0: reporter [obj2] In Object::do_compare(), res=0
UVM_INFO testbench.sv(128) @ 0: uvm_test_top [TEST] obj1 and obj2 are different
UVM_INFO testbench.sv(112) @ 0: uvm_test_top [TEST] Copy m_name
UVM_INFO testbench.sv(114) @ 0: uvm_test_top [TEST] Obj2.print:  m_name=obj1 m_bool=TRUE m_mode=0x9 m_addr=0x64c1
UVM_INFO testbench.sv(30) @ 0: reporter [m_pkt] In Packet::do_compare(), res=0
UVM_INFO testbench.sv(77) @ 0: reporter [obj2] In Object::do_compare(), res=0
UVM_INFO testbench.sv(128) @ 0: uvm_test_top [TEST] obj1 and obj2 are different
UVM_INFO testbench.sv(117) @ 0: uvm_test_top [TEST] Copy m_pkt.m_addr
UVM_INFO testbench.sv(119) @ 0: uvm_test_top [TEST] Obj2.print:  m_name=obj1 m_bool=TRUE m_mode=0x9 m_addr=0x3cb6
UVM_INFO testbench.sv(30) @ 0: reporter [m_pkt] In Packet::do_compare(), res=1
UVM_INFO testbench.sv(77) @ 0: reporter [obj2] In Object::do_compare(), res=1
UVM_INFO testbench.sv(126) @ 0: uvm_test_top [TEST] obj1 and obj2 are same
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_report_server.svh(847) @ 0: reporter [UVM/REPORT/SERVER]

This version gets the right answer, but compare its log with the macro version: there are no MISCMP lines. Plain == checks only produce a bit. The comparer never hears about the miscompare, so it cannot tell you which field failed.

Reporting Miscompares from do_compare()

The fix is to check each field through the comparer instead of with ==. uvm_comparer provides a method for each kind of field, and each one records and prints a miscompare in the same format the macros use.


virtual function bit do_compare(uvm_object rhs, uvm_comparer comparer);
  Object _obj;
  do_compare = super.do_compare(rhs, comparer);
  $cast(_obj, rhs);
  do_compare &= comparer.compare_field_int("m_mode", m_mode, _obj.m_mode);    // integral up to 64 bits
  do_compare &= comparer.compare_field_int("m_bool", m_bool, _obj.m_bool);    // enums are integral too
  do_compare &= comparer.compare_string   ("m_name", m_name, _obj.m_name);    // strings
  do_compare &= comparer.compare_object   ("m_pkt",  m_pkt,  _obj.m_pkt);     // nested objects
endfunction

compare_object() follows the comparer's policy for nested objects, so the same code can do a deep compare or just check that both handles point to the same object, depending on how the comparer is configured.

Quick Check

Q: In obj2.compare(obj1), which object's values appear as lhs in a miscompare message?

Show Answer

obj2's. The object the method is called on is the left-hand side, and the argument is the right-hand side. In the macro example, lhs = FALSE : rhs = TRUE means obj2 held FALSE and obj1 held TRUE.

Common Mistakes

Mistake #1: Using == to compare two object handles

Wrong: if (actual == expected)
Right: if (actual.compare(expected))

Why? == on class handles checks whether both handles point to the same object, not whether the field values match. Two separate transactions with identical contents still compare as unequal.

Mistake #2: Forgetting super.do_compare() in a derived class

If a transaction extends another transaction class, its do_compare() must include super.do_compare(rhs, comparer). Without it, every field declared in the parent class is silently skipped, and objects that differ only in those fields compare as equal.

Mistake #3: Using only == checks inside do_compare()

A do_compare() built from == checks returns the right bit but reports nothing. When a scoreboard check fails in a long regression, you then have to rerun with extra prints to find the field. Using compare_field_int(), compare_string() and compare_object() gives you the field name and both values in the log the first time.

Mistake #4: Expecting every differing field to be reported in one call

With the default comparer, show_max is 1, so a compare reports only the first miscompare. That is easy to misread as "only one field is wrong". Raise show_max on a comparer you pass in when you want the full list.

Summary

In this article, you learned:

  • compare() returns 1 on a match and 0 on a miscompare, and calls do_compare() to check the fields
  • The object the method is called on is lhs, and the argument is rhs
  • Field macros generate do_compare() and print a MISCMP message naming the failing field
  • The default comparer reports only the first miscompare because show_max defaults to 1
  • A hand-written do_compare() should use the comparer's compare_field_int(), compare_string() and compare_object() methods so failures are reported
Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5