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_comparercalls 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
rhsis the object to compare against. The object you call the method on is the left-hand side, soobj2.compare(obj1)treats obj2 as lhs and obj1 as rhs.compareris an optional policy object. When you leave it out, the globaluvm_default_compareris used.compare()is not virtual and should not be overridden. To include a class's own fields, overridedo_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
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
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 callsdo_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 aMISCMPmessage naming the failing field - The default comparer reports only the first miscompare because
show_maxdefaults to 1 - A hand-written
do_compare()should use the comparer'scompare_field_int(),compare_string()andcompare_object()methods so failures are reported