From e8b033a43055386e6efbc77c2f2c3aa1cebdb5d8 Mon Sep 17 00:00:00 2001 From: Peter Monsson Date: Thu, 20 Nov 2025 15:45:35 +0100 Subject: [PATCH 1/2] Adding uvm object comparison support --- .github/workflows/unit-tests.yml | 2 +- README.md | 2 +- src/uvmkit_check.sv | 57 +++++++++++- tests/stub/packet.sv | 53 +++++++++++ tests/stub/uvm_object.sv | 69 +++++++++++++++ tests/unit/uvm_object_unit_test.sv | 122 +++++++++++++++++++++++++ tests/unit/uvmkit_check_unit_test.sv | 128 +++++++++++++++++++++++++++ 7 files changed, 428 insertions(+), 5 deletions(-) create mode 100644 tests/stub/packet.sv create mode 100644 tests/stub/uvm_object.sv create mode 100644 tests/unit/uvm_object_unit_test.sv diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 6ae62ed..ba9f541 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -21,6 +21,6 @@ jobs: ref: master path: svunit - name: runSVUnit - run: (cd tests; SVUNIT_INSTALL=`pwd`/../svunit PATH=$PATH:$SVUNIT_INSTALL"/bin" runSVUnit -c -Wno-WIDTH -c_arg "-I../src" -c_arg "--assert" -c_arg "-j 0") + run: (cd tests; SVUNIT_INSTALL=`pwd`/../svunit PATH=$PATH:$SVUNIT_INSTALL"/bin" runSVUnit -c -Wno-WIDTH -c_arg "-I../src" -c_arg "-I../tests/stub" -c_arg "--assert" -c_arg "-j 0") - name: Check log run: '! grep "FAILED" tests/run.log; return $?;' diff --git a/README.md b/README.md index 91ea137..a8b57a1 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ How to use How to run unit tests --------------------- - SVUNIT_INSTALL=$PWD/../svunit PATH=$PATH:$PWD/../svunit/bin runSVUnit -c -Wno-WIDTH -o obj_dir -c_arg "-I../src" + SVUNIT_INSTALL=$PWD/../svunit PATH=$PATH:$PWD/../svunit/bin runSVUnit -c -Wno-WIDTH -o obj_dir -c_arg "-I../src" -c_arg "-I../tests/stub" Aternaltive way to run unit tests diff --git a/src/uvmkit_check.sv b/src/uvmkit_check.sv index ae204f3..582b814 100644 --- a/src/uvmkit_check.sv +++ b/src/uvmkit_check.sv @@ -12,7 +12,7 @@ // Use ifndef here so that we can inject testability to the checks `ifndef uvmkit_error_msg - `ifdef uvm_error + `ifdef UVMKIT_HAS_UVM `define uvmkit_error_msg(typ, msg) `uvm_error(typ, msg) `else `define uvmkit_error_msg(typ, msg) $error("[%s] %s", typ, msg); @@ -167,8 +167,59 @@ end \ end -// TODO: -// - uvm objects +//=================================== +// UVM Object Comparison Macros +//=================================== +// These macros are only available when UVM is defined by including uvm_macros.svh. +// This allows uvmkit_check to be used in environments without UVM. + +`ifdef uvm_error + +// uvmkit_check_equals_uvm - Checks if two UVM objects are equal using compare() +// Parameters: +// a - First uvm_object to compare +// b - Second uvm_object to compare +// msg - Optional message to append to error output +`define uvmkit_check_equals_uvm(a, b, msg="") \ + begin \ + automatic uvm_object _a_obj = a; \ + automatic uvm_object _b_obj = b; \ + bit _compare_result; \ + if (_a_obj == null || _b_obj == null) begin \ + `uvmkit_error_msg_fmt("uvmkit_check_equals_uvm", "Cannot compare null objects", msg) \ + end else begin \ + _compare_result = _a_obj.compare(_b_obj); \ + if (!_compare_result) begin \ + `uvmkit_error_msg_fmt("uvmkit_check_equals_uvm", $sformatf("UVM objects do not match:\n Type: %s vs %s", _a_obj.get_type_name(), _b_obj.get_type_name()), msg) \ + end \ + end \ + end + +// uvmkit_check_not_equals_uvm - Checks if two UVM objects are not equal using compare() +// Parameters: +// a - First uvm_object to compare +// b - Second uvm_object to compare +// msg - Optional message to append to error output +`define uvmkit_check_not_equals_uvm(a, b, msg="") \ + begin \ + automatic uvm_object _a_obj = a; \ + automatic uvm_object _b_obj = b; \ + bit _compare_result; \ + if (_a_obj == null || _b_obj == null) begin \ + // If one or both are null, they are different (unless both are null) \ + if (_a_obj == null && _b_obj == null) begin \ + `uvmkit_error_msg_fmt("uvmkit_check_not_equals_uvm", "Both objects are null, so they are equal", msg) \ + end \ + // else: one is null and one is not, so they are different - pass \ + end else begin \ + _compare_result = _a_obj.compare(_b_obj); \ + if (_compare_result) begin \ + `uvmkit_error_msg_fmt("uvmkit_check_not_equals_uvm", $sformatf("UVM objects match, but expected them to be different:\n Type: %s", _a_obj.get_type_name()), msg) \ + end \ + end \ + end + +`endif // uvm_error // TODOs pertaining to uvmkit_check_equals and uvmkit_check_not_equals // TODO: if both inputs are x but their sizes differ, then this is not equal. this is correct, but hard to debug. can we add size information to each expression or is this too hard? diff --git a/tests/stub/packet.sv b/tests/stub/packet.sv new file mode 100644 index 0000000..90a537e --- /dev/null +++ b/tests/stub/packet.sv @@ -0,0 +1,53 @@ +//----------------------------------------------------------------------------- +// packet - Example UVM object for testing uvmkit_check UVM comparison macros +//----------------------------------------------------------------------------- +// This is a simple example class derived from uvm_object that demonstrates +// how to implement do_compare() for field-level comparison. +//----------------------------------------------------------------------------- + +`include "uvm_object.sv" + +class packet extends uvm_object; + + // Packet fields + bit [31:0] addr; + bit [63:0] data; + bit [3:0] id; + + //----------------------------------------------------------------------------- + // Constructor + //----------------------------------------------------------------------------- + function new(string name = ""); + super.new(name); + addr = 0; + data = 0; + id = 0; + endfunction + + //----------------------------------------------------------------------------- + // get_type_name - Returns the type identifier for this object + //----------------------------------------------------------------------------- + virtual function string get_type_name(); + return "packet"; + endfunction + + //----------------------------------------------------------------------------- + // do_compare - Compares all fields of this packet with rhs + //----------------------------------------------------------------------------- + virtual function bit do_compare(uvm_object rhs); + packet rhs_packet; + + // Cast to packet type + if (!$cast(rhs_packet, rhs)) begin + return 0; // Type mismatch + end + + // Compare all fields + if (this.addr != rhs_packet.addr) return 0; + if (this.data != rhs_packet.data) return 0; + if (this.id != rhs_packet.id) return 0; + + return 1; // All fields match + endfunction + +endclass diff --git a/tests/stub/uvm_object.sv b/tests/stub/uvm_object.sv new file mode 100644 index 0000000..ae5972c --- /dev/null +++ b/tests/stub/uvm_object.sv @@ -0,0 +1,69 @@ +//----------------------------------------------------------------------------- +// uvm_object - Minimal UVM object stub for uvmkit_check +//----------------------------------------------------------------------------- +// This is a minimal implementation of uvm_object that provides just enough +// functionality for comparison checks. It is not a complete UVM implementation. +//----------------------------------------------------------------------------- + +class uvm_object; + + // Object name + protected string m_name; + + //----------------------------------------------------------------------------- + // Constructor + //----------------------------------------------------------------------------- + function new(string name = ""); + m_name = name; + endfunction + + //----------------------------------------------------------------------------- + // get_name - Returns the instance name of the object + //----------------------------------------------------------------------------- + virtual function string get_name(); + return m_name; + endfunction + + //----------------------------------------------------------------------------- + // get_type_name - Returns the type identifier for this object + // This should be overridden in derived classes to return the actual type name + //----------------------------------------------------------------------------- + virtual function string get_type_name(); + return "uvm_object"; + endfunction + + //----------------------------------------------------------------------------- + // compare - Deep compares this object with rhs + // Returns 1 if the objects match, 0 otherwise + //----------------------------------------------------------------------------- + virtual function bit compare(uvm_object rhs); + // Handle null case + if (rhs == null) begin + return 0; + end + + // If comparing against self, always equal + if (this == rhs) begin + return 1; + end + + // Type names must match + if (get_type_name() != rhs.get_type_name()) begin + return 0; + end + + // Call do_compare for field comparison + return do_compare(rhs); + endfunction + + //----------------------------------------------------------------------------- + // do_compare - User-overridable comparison hook + // Derived classes should override this to compare their specific fields + // Base implementation returns 1 (objects are equal if types match) + //----------------------------------------------------------------------------- + virtual function bit do_compare(uvm_object rhs); + // Base class has no fields to compare + return 1; + endfunction + +endclass diff --git a/tests/unit/uvm_object_unit_test.sv b/tests/unit/uvm_object_unit_test.sv new file mode 100644 index 0000000..72b2ef3 --- /dev/null +++ b/tests/unit/uvm_object_unit_test.sv @@ -0,0 +1,122 @@ +`include "svunit_defines.svh" + +module uvm_object_unit_test; + import svunit_pkg::svunit_testcase; + + string name = "uvm_object_ut"; + svunit_testcase svunit_ut; + + //=================================== + // This is the UUT that we're + // running the Unit Tests on + //=================================== + `include "uvm_object.sv" + + //=================================== + // Build + //=================================== + function void build(); + svunit_ut = new(name); + endfunction + + + //=================================== + // Setup for running the Unit Tests + //=================================== + task setup(); + svunit_ut.setup(); + /* Place Setup Code Here */ + endtask + + + //=================================== + // Here we deconstruct anything we + // need after running the Unit Tests + //=================================== + task teardown(); + svunit_ut.teardown(); + /* Place Teardown Code Here */ + endtask + + + //=================================== + // All tests are defined between the + // SVUNIT_TESTS_BEGIN/END macros + //=================================== + `SVUNIT_TESTS_BEGIN + + `SVTEST(uvm_object_new_test) + uvm_object obj; + obj = new("test_obj"); + `FAIL_IF(obj == null) + `SVTEST_END + + `SVTEST(uvm_object_get_name_test) + uvm_object obj; + string obj_name; + bit name_match; + obj = new("my_object"); + obj_name = obj.get_name(); + name_match = (obj_name == "my_object"); + `FAIL_UNLESS(name_match) + `SVTEST_END + + `SVTEST(uvm_object_get_name_empty_test) + uvm_object obj; + string obj_name; + bit name_match; + obj = new(""); + obj_name = obj.get_name(); + name_match = (obj_name == ""); + `FAIL_UNLESS(name_match) + `SVTEST_END + + `SVTEST(uvm_object_get_name_default_test) + uvm_object obj; + string obj_name; + bit name_match; + obj = new(); + obj_name = obj.get_name(); + name_match = (obj_name == ""); + `FAIL_UNLESS(name_match) + `SVTEST_END + + `SVTEST(uvm_object_get_type_name_test) + uvm_object obj; + string type_name; + bit type_match; + obj = new("test"); + type_name = obj.get_type_name(); + type_match = (type_name == "uvm_object"); + `FAIL_UNLESS(type_match) + `SVTEST_END + + `SVTEST(uvm_object_compare_same_object_test) + uvm_object obj; + bit result; + obj = new("test"); + result = obj.compare(obj); + `FAIL_UNLESS(result == 1) + `SVTEST_END + + `SVTEST(uvm_object_compare_null_test) + uvm_object obj; + bit result; + obj = new("test"); + result = obj.compare(null); + `FAIL_UNLESS(result == 0) + `SVTEST_END + + `SVTEST(uvm_object_compare_different_objects_test) + uvm_object obj1, obj2; + bit result; + obj1 = new("obj1"); + obj2 = new("obj2"); + // Base uvm_objects should compare equal (no fields to compare) + result = obj1.compare(obj2); + `FAIL_UNLESS(result == 1) + `SVTEST_END + + `SVUNIT_TESTS_END + +endmodule diff --git a/tests/unit/uvmkit_check_unit_test.sv b/tests/unit/uvmkit_check_unit_test.sv index 95de57a..0f8e423 100644 --- a/tests/unit/uvmkit_check_unit_test.sv +++ b/tests/unit/uvmkit_check_unit_test.sv @@ -28,7 +28,9 @@ module uvmkit_check_unit_test; bit error_called; string last_msg; `define uvmkit_error_msg(typ, msg) error_called = 1; last_msg = msg; + `define uvm_error `include "uvmkit_check.sv" + `include "packet.sv" //=================================== // Test enum type for enum comparisons @@ -709,6 +711,132 @@ module uvmkit_check_unit_test; `FAIL_UNLESS(ok) `SVTEST_END + `SVTEST(equals_uvm_basic_test) + packet p1, p2; + p1 = new("p1"); + p2 = new("p2"); + // Same values should pass + p1.addr = 32'h1234; + p1.data = 64'hDEADBEEF; + p1.id = 4'h5; + p2.addr = 32'h1234; + p2.data = 64'hDEADBEEF; + p2.id = 4'h5; + `uvmkit_check_equals_uvm(p1, p2) + `FAIL_IF(error_called) + // Different values should fail + error_called = 0; + p2.addr = 32'h5678; + `uvmkit_check_equals_uvm(p1, p2) + `FAIL_UNLESS(error_called) + `SVTEST_END + + `SVTEST(equals_uvm_null_test) + packet p1, p2; + p1 = new("p1"); + p2 = null; + `uvmkit_check_equals_uvm(p1, p2) + `FAIL_UNLESS(error_called) + error_called = 0; + p1 = null; + p2 = null; + `uvmkit_check_equals_uvm(p1, p2) + `FAIL_UNLESS(error_called) + `SVTEST_END + + `SVTEST(equals_uvm_same_object_test) + packet p1, p2; + p1 = new("p1"); + p1.addr = 32'h1234; + p2 = p1; // Same object reference + `uvmkit_check_equals_uvm(p1, p2) + `FAIL_IF(error_called) + `SVTEST_END + + `SVTEST(equals_uvm_type_mismatch_test) + packet p1; + uvm_object obj; + p1 = new("p1"); + obj = new("obj"); + `uvmkit_check_equals_uvm(p1, obj) + `FAIL_UNLESS(error_called) + `SVTEST_END + + `SVTEST(equals_uvm_with_message) + bit ok; + packet p1, p2; + p1 = new("p1"); + p2 = new("p2"); + p1.addr = 32'h1234; + p2.addr = 32'h5678; + `uvmkit_check_equals_uvm(p1, p2, "Packets should match") + `FAIL_UNLESS(error_called) + ok = endswith(last_msg, "Packets should match"); + `FAIL_UNLESS(ok) + `SVTEST_END + + `SVTEST(not_equals_uvm_basic_test) + packet p1, p2; + p1 = new("p1"); + p2 = new("p2"); + // Different values should pass + p1.addr = 32'h1234; + p2.addr = 32'h5678; + `uvmkit_check_not_equals_uvm(p1, p2) + `FAIL_IF(error_called) + // Same values should fail + error_called = 0; + p2.addr = 32'h1234; + p1.data = 64'hDEAD; + p2.data = 64'hDEAD; + p1.id = 4'h1; + p2.id = 4'h1; + `uvmkit_check_not_equals_uvm(p1, p2) + `FAIL_UNLESS(error_called) + `SVTEST_END + + `SVTEST(not_equals_uvm_null_test) + packet p1, p2; + p1 = new("p1"); + p2 = null; + `uvmkit_check_not_equals_uvm(p1, p2) + `FAIL_IF(error_called) + `SVTEST_END + + `SVTEST(not_equals_uvm_both_null_test) + packet p1, p2; + p1 = null; + p2 = null; + `uvmkit_check_not_equals_uvm(p1, p2) + `FAIL_UNLESS(error_called) + `SVTEST_END + + `SVTEST(not_equals_uvm_same_object_test) + packet p1, p2; + p1 = new("p1"); + p1.addr = 32'h1234; + p2 = p1; // Same object reference + `uvmkit_check_not_equals_uvm(p1, p2) + `FAIL_UNLESS(error_called) + `SVTEST_END + + `SVTEST(not_equals_uvm_with_message) + bit ok; + packet p1, p2; + p1 = new("p1"); + p2 = new("p2"); + p1.addr = 32'h1234; + p1.data = 64'hDEAD; + p1.id = 4'h5; + p2.addr = 32'h1234; + p2.data = 64'hDEAD; + p2.id = 4'h5; + `uvmkit_check_not_equals_uvm(p1, p2, "Packets should be different") + `FAIL_UNLESS(error_called) + ok = endswith(last_msg, "Packets should be different"); + `FAIL_UNLESS(ok) + `SVTEST_END + `SVUNIT_TESTS_END endmodule From c9c386ad560cc079de5e5c0080f8170785959079 Mon Sep 17 00:00:00 2001 From: Peter Monsson Date: Thu, 20 Nov 2025 15:48:05 +0100 Subject: [PATCH 2/2] Fix claude messing things up --- src/uvmkit_check.sv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uvmkit_check.sv b/src/uvmkit_check.sv index 582b814..97e819f 100644 --- a/src/uvmkit_check.sv +++ b/src/uvmkit_check.sv @@ -12,7 +12,7 @@ // Use ifndef here so that we can inject testability to the checks `ifndef uvmkit_error_msg - `ifdef UVMKIT_HAS_UVM + `ifdef uvm_error `define uvmkit_error_msg(typ, msg) `uvm_error(typ, msg) `else `define uvmkit_error_msg(typ, msg) $error("[%s] %s", typ, msg);