[dv, cosim] Enable lock-step PC and GPR verification with MPACT-Sim

Implements the full end-to-end co-simulation comparison for scalar
instructions (PC and GPRs).

The `kelvin_cosim_checker` in UVM is to:
- Call the DPI functions to get the golden state from the simulator.
- Compare the retired PC and GPR writeback data from the DUT's RVVI
  trace against the MPACT-Sim state.
- Report `COSIM_PC_MISMATCH` or `COSIM_GPR_MISMATCH` upon failure.

Change-Id: I5d7f02c6d347b4308bc4ea8a38f08ce2407659e0
diff --git a/tests/uvm/Makefile b/tests/uvm/Makefile
index 8f121e3..44aa59f 100644
--- a/tests/uvm/Makefile
+++ b/tests/uvm/Makefile
@@ -12,37 +12,36 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# Makefile for Kelvin UVM Testbench
+# Makefile for Kelvin UVM Testbench (Integrates with MPACT-Sim)
 
-# --- User Configuration ---
-# Tool Configuration
+# Check if ROOTDIR is set. If not, exit with an error.
+ifndef ROOTDIR
+    $(error ROOTDIR is not set. Please define it, e.g., export ROOTDIR=$(abspath ../../../..))
+endif
+
+# User Configuration
 VCS = vcs
+CXX = clang++
 
-# Directories
+# UVM DV Directories
 RTL_DIR = ./rtl
 COMMON_DIR = ./common
 TB_DIR = ./tb
 ENV_DIR = ./env
 TESTS_DIR = ./tests
 BIN_DIR = ./bin
+
+# Simulation Work Directory
 SIM_DIR = ./sim_work
 LOG_DIR = $(SIM_DIR)/logs
 WAVE_DIR = $(SIM_DIR)/waves
 
-# Source Files
-DUT_RTL = $(RTL_DIR)/RvvCoreMiniVerificationAxi.sv
-IF_FILES = $(COMMON_DIR)/kelvin_axi_master/kelvin_axi_master_if.sv \
-           $(COMMON_DIR)/kelvin_axi_slave/kelvin_axi_slave_if.sv \
-           $(COMMON_DIR)/kelvin_irq/kelvin_irq_if.sv
-TRANS_PKG_FILE = $(COMMON_DIR)/transaction_item/transaction_item_pkg.sv
-AXI_MASTER_AGENT_PKG = $(COMMON_DIR)/kelvin_axi_master/kelvin_axi_master_agent_pkg.sv
-AXI_SLAVE_AGENT_PKG = $(COMMON_DIR)/kelvin_axi_slave/kelvin_axi_slave_agent_pkg.sv
-IRQ_AGENT_PKG = $(COMMON_DIR)/kelvin_irq/kelvin_irq_agent_pkg.sv
-ENV_PKG = $(ENV_DIR)/kelvin_env_pkg.sv
-TEST_PKG = $(TESTS_DIR)/kelvin_test_pkg.sv
-TB_TOP_FILE = $(TB_DIR)/kelvin_tb_top.sv
+# MPACT-Sim Integration Paths
+MPACT_DIR = $(ROOTDIR)/sim/kelvin
+MPACT_BAZEL_BIN_DIR = $(MPACT_DIR)/bazel-bin/sim/cosim
+MPACT_COSIM_LIB_NAME = kelvin_cosim_lib_static
 
-# File List for VCS -f option (Assumed to exist and be checked in)
+# File List for VCS -f option
 FILE_LIST = ./kelvin_dv.f
 
 # Simulation Settings
@@ -51,14 +50,14 @@
 TEST_BINARY ?= $(BIN_DIR)/program.bin
 UVM_VERBOSITY ?= UVM_MEDIUM
 TEST_TIMEOUT_NS ?= 20000
-PLUSARGS = +UVM_TESTNAME=$(UVM_TESTNAME) +TEST_BINARY=$(TEST_BINARY) +UVM_VERBOSITY=$(UVM_VERBOSITY) +TEST_TIMEOUT=$(TEST_TIMEOUT_NS)
+PLUSARGS = +UVM_TESTNAME=$(UVM_TESTNAME) +TEST_BINARY=$(TEST_BINARY) \
+           +UVM_VERBOSITY=$(UVM_VERBOSITY) +TEST_TIMEOUT=$(TEST_TIMEOUT_NS)
 
 # Waveform Dumping
 DUMP_WAVES = 1
 WAVE_FILE = $(WAVE_DIR)/$(UVM_TESTNAME).fsdb
 
-# --- VCS Options ---
-# Base compile options
+# VCS Options
 VCS_COMPILE_OPTS = \
 	-full64 \
 	-sverilog \
@@ -69,7 +68,14 @@
 	-timescale=1ns/1ps \
 	-o $(SIM_EXEC)
 
-# Base run options
+# Add C++ compiler/linker options for MPACT-Sim
+VCS_COMPILE_OPTS += \
+    -cpp $(CXX) \
+    -cppflags "-std=c++17" \
+    -CFLAGS "-I$(MPACT_DIR)" \
+    -L$(MPACT_BAZEL_BIN_DIR) \
+    -l$(MPACT_COSIM_LIB_NAME)
+
 VCS_RUN_OPTS = \
 	$(PLUSARGS)
 
@@ -80,9 +86,9 @@
                 +fsdbfile+$(WAVE_FILE)
 endif
 
-# --- Targets ---
+# Targets
 
-.PHONY: all compile run clean dirs help
+.PHONY: all compile run clean dirs help build_mpact_lib
 
 # Default target
 all: run
@@ -91,8 +97,13 @@
 dirs:
 	@mkdir -p $(SIM_DIR) $(LOG_DIR) $(WAVE_DIR) $(BIN_DIR)
 
+# Build the MPACT-Sim library using Bazel with CC=clang
+build_mpact_lib:
+	@echo "--- Building MPACT-Sim Co-sim Library via Bazel ---"
+	cd $(MPACT_DIR) && CC=clang bazel build //sim/cosim:$(MPACT_COSIM_LIB_NAME)
+
 # Compile the design
-compile: dirs
+compile: dirs build_mpact_lib
 	@echo "--- Compiling with VCS ---"
 	$(VCS) $(VCS_COMPILE_OPTS) -l $(LOG_DIR)/compile.log -f $(FILE_LIST)
 	@echo "--- Compilation Finished ---"
@@ -116,13 +127,16 @@
 clean:
 	@echo "--- Cleaning Simulation Files ---"
 	rm -rf $(SIM_DIR) simv* csrc* *.log* *.key *.vpd *.fsdb ucli.key DVEfiles/ verdiLog/ novas.*
+	@echo "--- Cleaning MPACT-Sim Bazel cache ---"
+	cd $(MPACT_DIR) && bazel clean
 
-# --- Help ---
+# Help
 help:
 	@echo "Makefile Targets:"
-	@echo "  make compile       : Compiles the DUT and testbench (using $(FILE_LIST))"
-	@echo "  make run           : Compiles (if needed) and runs the simulation"
-	@echo "                     :   Override defaults: make run UVM_TESTNAME=<test> TEST_BINARY=<path> UVM_VERBOSITY=<level>"
-	@echo "  make clean         : Removes generated simulation files"
-	@echo "  make dirs          : Creates simulation directories"
-	@echo "  make help          : Shows this help message"
+	@echo "  make compile          : Builds MPACT lib and compiles DUT/testbench"
+	@echo "  make run              : Builds and runs the full co-simulation"
+	@echo "                        :   Override defaults: make run UVM_TESTNAME=<test> TEST_BINARY=<path> UVM_VERBOSITY=<level>"
+	@echo "  make build_mpact_lib  : Only builds the MPACT-Sim C++ library"
+	@echo "  make clean            : Removes generated simulation and MPACT-Sim files"
+	@echo "  make dirs             : Creates simulation directories"
+	@echo "  make help             : Shows this help message"
diff --git a/tests/uvm/bin/program.bin b/tests/uvm/bin/program.bin
index 0ae87ca..803908a 100644
--- a/tests/uvm/bin/program.bin
+++ b/tests/uvm/bin/program.bin
Binary files differ
diff --git a/tests/uvm/common/cosim/kelvin_cosim_checker_pkg.sv b/tests/uvm/common/cosim/kelvin_cosim_checker_pkg.sv
index 72097f9..167847a 100644
--- a/tests/uvm/common/cosim/kelvin_cosim_checker_pkg.sv
+++ b/tests/uvm/common/cosim/kelvin_cosim_checker_pkg.sv
@@ -20,6 +20,19 @@
 
   import uvm_pkg::*;
   `include "uvm_macros.svh"
+  import kelvin_cosim_dpi_if::*;
+
+  //----------------------------------------------------------------------------
+  // Struct: retired_instr_info_s
+  // Description: A struct to hold information about a single retired
+  //              instruction, captured from the RVVI trace.
+  //----------------------------------------------------------------------------
+  typedef struct {
+    logic [31:0] pc;
+    logic [31:0] insn;
+    logic [31:0] x_wb;
+    int          retire_index; // Original index from the RVVI bus
+  } retired_instr_info_s;
 
   //----------------------------------------------------------------------------
   // Class: kelvin_cosim_checker
@@ -66,43 +79,123 @@
 
     // Run phase: Contains the main co-simulation loop
     virtual task run_phase(uvm_phase phase);
-      // TODO: Initialize the MPACT simulator.
-      `uvm_info("COSIM_STUB", "MPACT simulator would be initialized now.",
-                UVM_MEDIUM);
+      retired_instr_info_s retired_instr_q[$];
+      int unsigned num_retired_this_cycle;
+      int unsigned mpact_pc;
+      logic [31:0] rtl_instr;
 
+      if (mpact_init() != 0)
+        `uvm_fatal(get_type_name(), "MPACT simulator DPI init failed.")
 
       // Main co-simulation loop
       forever begin
         // Wait for the RVVI monitor to signal an instruction retirement
         instruction_retired_event.wait_trigger();
+        retired_instr_q.delete();
 
-        // This is a simplified example for one retirement channel (channel 0).
-        // A full implementation would loop through all NRET channels.
-        if (rvvi_vif.valid[0][0]) begin // Assuming NHART=1, check hart 0
-            // Get the retired instruction from the DUT's trace
-            logic [31:0] retired_instruction = rvvi_vif.insn[0][0];
+        // Collect all retired instructions and their state from the RVVI trace
+        for (int i = 0; i < rvvi_vif.RETIRE; i++) begin
+          if (rvvi_vif.valid[0][i]) begin
+            retired_instr_info_s info;
+            info.pc = rvvi_vif.pc_rdata[0][i];
+            info.insn = rvvi_vif.insn[0][i];
+            info.x_wb = rvvi_vif.x_wb[0][i];
+            info.retire_index = i; // Store the original channel index
+            retired_instr_q.push_back(info);
+            `uvm_info(get_type_name(),
+              $sformatf("RTL Retired: PC=0x%h, Insn=0x%h", info.pc, info.insn),
+              UVM_HIGH)
+          end
+        end
+        num_retired_this_cycle = retired_instr_q.size();
 
-            `uvm_info("COSIM_STUB",
-                      $sformatf("DUT retired instruction 0x%h",
-                                retired_instruction), UVM_HIGH);
+        for (int i = 0; i < num_retired_this_cycle; i++) begin
+          bit pc_match_found = 0;
+          int match_index = -1;
 
-            // TODO: Send this specific instruction to the MPACT simulator to
-            //       execute, then call comparison task.
-            step_and_compare();
+          mpact_pc = mpact_get_pc();
+
+          foreach (retired_instr_q[j]) begin
+            if (retired_instr_q[j].pc == mpact_pc) begin
+              pc_match_found = 1;
+              match_index = j;
+              break;
+            end
+          end
+
+          if (!pc_match_found) begin
+            string rtl_pcs_str = "[ ";
+            foreach (retired_instr_q[j]) begin
+              rtl_pcs_str = $sformatf("%s0x%h ", rtl_pcs_str,
+                                      retired_instr_q[j].pc);
+            end
+            rtl_pcs_str = {rtl_pcs_str, "]"};
+            `uvm_error("COSIM_PC_MISMATCH",
+              $sformatf("MPACT PC 0x%h mismatches retired RTL PCs: %s",
+                        mpact_pc, rtl_pcs_str))
+            phase.drop_objection(this, "Terminating on PC mismatch.");
+            return;
+          end
+
+          rtl_instr = retired_instr_q[match_index].insn;
+          `uvm_info(get_type_name(),
+                    $sformatf("PC match (0x%h). Stepping MPACT with 0x%h",
+                              mpact_pc, rtl_instr), UVM_HIGH)
+
+          if (mpact_step(rtl_instr) != 0) begin
+            `uvm_error("COSIM_STEP_FAIL", "mpact_step() DPI call failed.")
+            phase.drop_objection(this, "Terminating on MPACT step fail.");
+            return;
+          end
+
+          // Check return status and terminate on failure
+          if (!step_and_compare(retired_instr_q[match_index])) begin
+            phase.drop_objection(this, "Terminating on GPR mismatch.");
+            return;
+          end
+
+          retired_instr_q.delete(match_index);
         end
       end
     endtask
 
-    // Task to get state from DUT and MPACT simulator and compare them
-    virtual task step_and_compare();
-      // TODO: Implement the full state comparison.
-      //       1. Make DPI calls to get post-execution state (PC, GPRs, CSRs)
-      //          from MPACT simulator.
-      //       2. Get the same post-execution state from the DUT via the RVVI
-      //          virtual interface.
-      //       3. Compare the RTL state against the MPACT simulator state and
-      //          report any mismatches.
-    endtask
+    virtual function bit step_and_compare(retired_instr_info_s rtl_info);
+      int unsigned mpact_gpr_val;
+      int unsigned rd_index;
+      logic [31:0] rtl_wdata;
+
+      `uvm_info(get_type_name(), "Comparing GPR writeback state...", UVM_HIGH)
+
+      if (!$onehot0(rtl_info.x_wb)) begin
+        `uvm_error("COSIM_GPR_MISMATCH",
+          $sformatf("Invalid GPR writeback flag at PC 0x%h. x_wb is not one-hot: 0x%h",
+                    rtl_info.pc, rtl_info.x_wb))
+        return 0; // FAIL
+      end
+
+      if (rtl_info.x_wb == 1) begin
+        `uvm_error("COSIM_GPR_MISMATCH",
+          $sformatf("Illegal write to x0 detected at PC 0x%h.", rtl_info.pc))
+        return 0; // FAIL
+      end
+      else if (rtl_info.x_wb != 0) begin
+        rd_index = $clog2(rtl_info.x_wb);
+        mpact_gpr_val = mpact_get_gpr(rd_index);
+
+        // Get the specific write data from the correct retire channel and register index
+        rtl_wdata = rvvi_vif.x_wdata[0][rtl_info.retire_index][rd_index];
+
+        if (mpact_gpr_val != rtl_wdata) begin
+          `uvm_error("COSIM_GPR_MISMATCH",
+            $sformatf("GPR[x%0d] mismatch at PC 0x%h. RTL: 0x%h, MPACT: 0x%h",
+                      rd_index, rtl_info.pc,
+                      rtl_wdata, mpact_gpr_val))
+          return 0; // FAIL
+        end
+      end
+      // If we reach here, all checks passed for this instruction.
+      return 1; // PASS
+    endfunction
 
   endclass : kelvin_cosim_checker
 
diff --git a/tests/uvm/common/cosim/kelvin_cosim_dpi_if.sv b/tests/uvm/common/cosim/kelvin_cosim_dpi_if.sv
new file mode 100644
index 0000000..ce111f4
--- /dev/null
+++ b/tests/uvm/common/cosim/kelvin_cosim_dpi_if.sv
@@ -0,0 +1,57 @@
+// Copyright 2025 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//----------------------------------------------------------------------------
+// Package: kelvin_cosim_dpi_if
+// Description: Defines the DPI-C import declarations for interacting with the
+//              MPACT simulator. This acts as the SystemVerilog-side "header".
+//----------------------------------------------------------------------------
+package kelvin_cosim_dpi_if;
+
+  // Function to initialize the MPACT simulator.
+  // Returns 0 on success.
+  import "DPI-C" context function int mpact_init();
+
+  // Function to reset the MPACT simulator.
+  // Returns 0 on success.
+  import "DPI-C" context function int mpact_reset();
+
+  // Function to execute one instruction in the MPACT simulator.
+  // Returns 0 on success.
+  import "DPI-C" context function int mpact_step(
+    input logic [31:0] instruction
+  );
+
+  // Function to check if the MPACT simulator has halted.
+  // Returns '1' (true) if halted.
+  import "DPI-C" context function bit mpact_is_halted();
+
+  // Function to get the program counter.
+  import "DPI-C" context function int unsigned mpact_get_pc();
+
+  // Function to get a general-purpose register value.
+  import "DPI-C" context function int unsigned mpact_get_gpr(
+    input int unsigned index
+  );
+
+  // Function to get a control and status register value.
+  import "DPI-C" context function int unsigned mpact_get_csr(
+    input int unsigned address
+  );
+
+  // Function to finalize the MPACT simulator.
+  // Returns 0 on success.
+  import "DPI-C" context function int mpact_fini();
+
+endpackage : kelvin_cosim_dpi_if
diff --git a/tests/uvm/common/cosim/kelvin_rvvi_agent_pkg.sv b/tests/uvm/common/cosim/kelvin_rvvi_agent_pkg.sv
index 62969fb..64cebc5 100644
--- a/tests/uvm/common/cosim/kelvin_rvvi_agent_pkg.sv
+++ b/tests/uvm/common/cosim/kelvin_rvvi_agent_pkg.sv
@@ -75,10 +75,10 @@
         @(posedge rvvi_vif.clk);
 
         for (int i = 0; i < RETIRE; i++) begin
-          if (rvvi_vif.valid[i][0]) begin // Assuming NHART=1
+          if (rvvi_vif.valid[0][i]) begin // Assuming NHART=1
             `uvm_info(get_type_name(),
                       $sformatf("Instruction retired on channel %0d, PC: 0x%h",
-                                i, rvvi_vif.pc_rdata[i][0]), UVM_HIGH)
+                                i, rvvi_vif.pc_rdata[0][i]), UVM_HIGH)
             any_instruction_retired = 1'b1;
           end
         end
diff --git a/tests/uvm/kelvin_dv.f b/tests/uvm/kelvin_dv.f
index bee6870..2f725f2 100644
--- a/tests/uvm/kelvin_dv.f
+++ b/tests/uvm/kelvin_dv.f
@@ -39,8 +39,9 @@
 ./common/kelvin_axi_master/kelvin_axi_master_if.sv
 ./common/kelvin_axi_slave/kelvin_axi_slave_if.sv
 ./common/kelvin_irq/kelvin_irq_if.sv
+./common/cosim/kelvin_cosim_dpi_if.sv
 
-// UVM Packages
+// UVM Packages (in dependency order)
 ./common/transaction_item/transaction_item_pkg.sv
 ./common/kelvin_axi_master/kelvin_axi_master_agent_pkg.sv
 ./common/kelvin_axi_slave/kelvin_axi_slave_agent_pkg.sv
diff --git a/tests/uvm/tests/kelvin_test_pkg.sv b/tests/uvm/tests/kelvin_test_pkg.sv
index 4949d6f..27e61c8 100644
--- a/tests/uvm/tests/kelvin_test_pkg.sv
+++ b/tests/uvm/tests/kelvin_test_pkg.sv
@@ -52,7 +52,7 @@
       req.prot = 3'b000;
       req.data.delete();
       req.strb.delete();
-      req.data.push_back(128'h00000034_00000000); // PC value
+      req.data.push_back(128'h00000000_00000000); // PC value
       req.strb.push_back('1); // Write all bytes
       finish_item(req);
       `uvm_info(get_type_name(),