RVA23: Add Zihpm CSRs.

The hardware performance counters now exist but don't do anything since they are hardware / implementation specific.

PiperOrigin-RevId: 828994924
Change-Id: I17534f8dd59ed2da4aa326a0cd257e34cf65450e
diff --git a/riscv/riscv_csr.h b/riscv/riscv_csr.h
index 53e40b8..eacf424 100644
--- a/riscv/riscv_csr.h
+++ b/riscv/riscv_csr.h
@@ -68,10 +68,68 @@
   kCycle = 0xc00,
   kTime = 0xc01,
   kInstret = 0xc02,
+  kHpmcounter3 = 0xc03,
+  kHpmcounter4 = 0xc04,
+  kHpmcounter5 = 0xc05,
+  kHpmcounter6 = 0xc06,
+  kHpmcounter7 = 0xc07,
+  kHpmcounter8 = 0xc08,
+  kHpmcounter9 = 0xc09,
+  kHpmcounter10 = 0xc0a,
+  kHpmcounter11 = 0xc0b,
+  kHpmcounter12 = 0xc0c,
+  kHpmcounter13 = 0xc0d,
+  kHpmcounter14 = 0xc0e,
+  kHpmcounter15 = 0xc0f,
+  kHpmcounter16 = 0xc10,
+  kHpmcounter17 = 0xc11,
+  kHpmcounter18 = 0xc12,
+  kHpmcounter19 = 0xc13,
+  kHpmcounter20 = 0xc14,
+  kHpmcounter21 = 0xc15,
+  kHpmcounter22 = 0xc16,
+  kHpmcounter23 = 0xc17,
+  kHpmcounter24 = 0xc18,
+  kHpmcounter25 = 0xc19,
+  kHpmcounter26 = 0xc1a,
+  kHpmcounter27 = 0xc1b,
+  kHpmcounter28 = 0xc1c,
+  kHpmcounter29 = 0xc1d,
+  kHpmcounter30 = 0xc1e,
+  kHpmcounter31 = 0xc1f,
 
   kCycleH = 0xc80,
   kTimeH = 0xc81,
   kInstretH = 0x82,
+  kHpmcounter3H = 0xc83,
+  kHpmcounter4H = 0xc84,
+  kHpmcounter5H = 0xc85,
+  kHpmcounter6H = 0xc86,
+  kHpmcounter7H = 0xc87,
+  kHpmcounter8H = 0xc88,
+  kHpmcounter9H = 0xc89,
+  kHpmcounter10H = 0xc8a,
+  kHpmcounter11H = 0xc8b,
+  kHpmcounter12H = 0xc8c,
+  kHpmcounter13H = 0xc8d,
+  kHpmcounter14H = 0xc8e,
+  kHpmcounter15H = 0xc8f,
+  kHpmcounter16H = 0xc90,
+  kHpmcounter17H = 0xc91,
+  kHpmcounter18H = 0xc92,
+  kHpmcounter19H = 0xc93,
+  kHpmcounter20H = 0xc94,
+  kHpmcounter21H = 0xc95,
+  kHpmcounter22H = 0xc96,
+  kHpmcounter23H = 0xc97,
+  kHpmcounter24H = 0xc98,
+  kHpmcounter25H = 0xc99,
+  kHpmcounter26H = 0xc9a,
+  kHpmcounter27H = 0xc9b,
+  kHpmcounter28H = 0xc9c,
+  kHpmcounter29H = 0xc9d,
+  kHpmcounter30H = 0xc9e,
+  kHpmcounter31H = 0xc9f,
 
   // Ignoring perf monitoring counters for now.
 
@@ -145,6 +203,35 @@
 
   kMCycle = 0xb00,    // Machine cycle counter.
   kMInstret = 0xb02,  // Machine instructions-retired counter.
+  kMhpmcounter3 = 0xb03,
+  kMhpmcounter4 = 0xb04,
+  kMhpmcounter5 = 0xb05,
+  kMhpmcounter6 = 0xb06,
+  kMhpmcounter7 = 0xb07,
+  kMhpmcounter8 = 0xb08,
+  kMhpmcounter9 = 0xb09,
+  kMhpmcounter10 = 0xb0a,
+  kMhpmcounter11 = 0xb0b,
+  kMhpmcounter12 = 0xb0c,
+  kMhpmcounter13 = 0xb0d,
+  kMhpmcounter14 = 0xb0e,
+  kMhpmcounter15 = 0xb0f,
+  kMhpmcounter16 = 0xb10,
+  kMhpmcounter17 = 0xb11,
+  kMhpmcounter18 = 0xb12,
+  kMhpmcounter19 = 0xb13,
+  kMhpmcounter20 = 0xb14,
+  kMhpmcounter21 = 0xb15,
+  kMhpmcounter22 = 0xb16,
+  kMhpmcounter23 = 0xb17,
+  kMhpmcounter24 = 0xb18,
+  kMhpmcounter25 = 0xb19,
+  kMhpmcounter26 = 0xb1a,
+  kMhpmcounter27 = 0xb1b,
+  kMhpmcounter28 = 0xb1c,
+  kMhpmcounter29 = 0xb1d,
+  kMhpmcounter30 = 0xb1e,
+  kMhpmcounter31 = 0xb1f,
 
   // Ignoring machine performance counters for now.
 
@@ -154,6 +241,35 @@
 
   kMCycleH = 0xb80,    // Upper 32 bits of mcycle.
   kMInstretH = 0xb82,  // Upper 32 bits of MInstret
+  kMhpmcounter3H = 0xb83,
+  kMhpmcounter4H = 0xb84,
+  kMhpmcounter5H = 0xb85,
+  kMhpmcounter6H = 0xb86,
+  kMhpmcounter7H = 0xb87,
+  kMhpmcounter8H = 0xb88,
+  kMhpmcounter9H = 0xb89,
+  kMhpmcounter10H = 0xb8a,
+  kMhpmcounter11H = 0xb8b,
+  kMhpmcounter12H = 0xb8c,
+  kMhpmcounter13H = 0xb8d,
+  kMhpmcounter14H = 0xb8e,
+  kMhpmcounter15H = 0xb8f,
+  kMhpmcounter16H = 0xb90,
+  kMhpmcounter17H = 0xb91,
+  kMhpmcounter18H = 0xb92,
+  kMhpmcounter19H = 0xb93,
+  kMhpmcounter20H = 0xb94,
+  kMhpmcounter21H = 0xb95,
+  kMhpmcounter22H = 0xb96,
+  kMhpmcounter23H = 0xb97,
+  kMhpmcounter24H = 0xb98,
+  kMhpmcounter25H = 0xb99,
+  kMhpmcounter26H = 0xb9a,
+  kMhpmcounter27H = 0xb9b,
+  kMhpmcounter28H = 0xb9c,
+  kMhpmcounter29H = 0xb9d,
+  kMhpmcounter30H = 0xb9e,
+  kMhpmcounter31H = 0xb9f,
 
   // Ignoring machine counter setup for now.
 
diff --git a/riscv/riscv_state.cc b/riscv/riscv_state.cc
index f7f6321..135bbf9 100644
--- a/riscv/riscv_state.cc
+++ b/riscv/riscv_state.cc
@@ -289,6 +289,59 @@
         nullptr);
   }
 
+  // hpmcounterN + hpmcounterNh (N=3..31)
+  uint32_t hpmcounter_base = static_cast<uint32_t>(RiscVCsrEnum::kCycle);
+  uint32_t hpmcounter_base_high = static_cast<uint32_t>(RiscVCsrEnum::kCycleH);
+  for (int i = 0; i < kNumHardwarePerfCounters; i++) {
+    RiscVCounterCsr<T, RiscVState>* hpmcounter =
+        CreateCsr<RiscVCounterCsr<T, RiscVState>>(
+            state, csr_vec,
+            absl::StrCat("hpmcounter", i + kMinimumHardwarePerfIndex),
+            static_cast<RiscVCsrEnum>(hpmcounter_base + i +
+                                      kMinimumHardwarePerfIndex),
+            state);
+    CHECK_NE(hpmcounter, nullptr);
+    if (std::is_same_v<T, uint32_t>) {
+      CHECK_NE(
+          CreateCsr<RiscVCounterCsrHigh<RiscVState>>(
+              state, csr_vec,
+              absl::StrCat("hpmcounter", i + kMinimumHardwarePerfIndex, "h"),
+              static_cast<RiscVCsrEnum>(hpmcounter_base_high + i +
+                                        kMinimumHardwarePerfIndex),
+              state,
+              reinterpret_cast<RiscVCounterCsr<uint32_t, RiscVState>*>(
+                  hpmcounter)),
+          nullptr);
+    }
+  }
+
+  // mhpmcounterN + mhpmcounterNh (N=3..31)
+  uint32_t mhpmcounter_base = static_cast<uint32_t>(RiscVCsrEnum::kMCycle);
+  uint32_t mhpmcounter_base_high =
+      static_cast<uint32_t>(RiscVCsrEnum::kMCycleH);
+  for (int i = 0; i < kNumHardwarePerfCounters; i++) {
+    RiscVCounterCsr<T, RiscVState>* mhpmcounter =
+        CreateCsr<RiscVCounterCsr<T, RiscVState>>(
+            state, csr_vec,
+            absl::StrCat("mhpmcounter", i + kMinimumHardwarePerfIndex),
+            static_cast<RiscVCsrEnum>(mhpmcounter_base + i +
+                                      kMinimumHardwarePerfIndex),
+            state);
+    CHECK_NE(mhpmcounter, nullptr);
+    if (std::is_same_v<T, uint32_t>) {
+      CHECK_NE(
+          CreateCsr<RiscVCounterCsrHigh<RiscVState>>(
+              state, csr_vec,
+              absl::StrCat("mhpmcounter", i + kMinimumHardwarePerfIndex, "h"),
+              static_cast<RiscVCsrEnum>(mhpmcounter_base_high + i +
+                                        kMinimumHardwarePerfIndex),
+              state,
+              reinterpret_cast<RiscVCounterCsr<uint32_t, RiscVState>*>(
+                  mhpmcounter + kMinimumHardwarePerfIndex)),
+          nullptr);
+    }
+  }
+
   // Hypervisor level CSRs
 
   // henvcfg
diff --git a/riscv/riscv_state.h b/riscv/riscv_state.h
index f04d56d..94196d4 100644
--- a/riscv/riscv_state.h
+++ b/riscv/riscv_state.h
@@ -43,6 +43,9 @@
 namespace sim {
 namespace riscv {
 
+static constexpr int kNumHardwarePerfCounters = 29;
+static constexpr int kMinimumHardwarePerfIndex = 3;
+
 using ArchState = ::mpact::sim::generic::ArchState;
 using DataBuffer = ::mpact::sim::generic::DataBuffer;
 using Instruction = ::mpact::sim::generic::Instruction;
diff --git a/riscv/riscv_top.cc b/riscv/riscv_top.cc
index b0ae232..d83ef4e 100644
--- a/riscv/riscv_top.cc
+++ b/riscv/riscv_top.cc
@@ -129,6 +129,13 @@
   memory_watcher_ = new util::MemoryWatcher(state_->memory());
   state_->set_memory(memory_watcher_);
 
+  counter_hardware_perf_.resize(kNumHardwarePerfCounters);
+  for (int i = 0; i < kNumHardwarePerfCounters; i++) {
+    counter_hardware_perf_[i].Initialize(absl::StrCat("hardware_perf_", i), 0);
+    CHECK_OK(AddCounter(&counter_hardware_perf_[i]))
+        << "Failed to register hardware_perf counter";
+  }
+
   // Register instruction and cycle counters.
   CHECK_OK(AddCounter(&counter_num_instructions_))
       << "Failed to register instruction counter";
@@ -154,6 +161,20 @@
   CHECK_OK(SetCsrCounter("cycle", counter_num_cycles_));
   CHECK_OK(SetCsrCounter("time", counter_num_cycles_));
 
+  // Add Zihpm counters - Unprivileged.
+  for (int i = 0; i < kNumHardwarePerfCounters; i++) {
+    std::string name =
+        absl::StrCat("hpmcounter", i + kMinimumHardwarePerfIndex);
+    CHECK_OK(SetCsrCounter(name, counter_hardware_perf_[i]));
+  }
+
+  // Add Zihpm counters - Machine Privileged.
+  for (int i = 0; i < kNumHardwarePerfCounters; i++) {
+    std::string name =
+        absl::StrCat("mhpmcounter", i + kMinimumHardwarePerfIndex);
+    CHECK_OK(SetCsrCounter(name, counter_hardware_perf_[i]));
+  }
+
   // Set up break and action points.
   rv_action_point_memory_interface_ = new RiscVActionPointMemoryInterface(
       state_->memory(),
diff --git a/riscv/riscv_top.h b/riscv/riscv_top.h
index 22ca7f9..028f9a7 100644
--- a/riscv/riscv_top.h
+++ b/riscv/riscv_top.h
@@ -145,6 +145,9 @@
     return &counter_num_cycles_;
   }
   generic::SimpleCounter<uint64_t>* counter_pc() { return &counter_pc_; }
+  std::vector<generic::SimpleCounter<uint64_t>>* counter_hardware_perf() {
+    return &counter_hardware_perf_;
+  }
   // Memory watchers used for data watch points.
   util::MemoryWatcher* memory_watcher() { return memory_watcher_; }
 
@@ -218,6 +221,8 @@
   std::vector<generic::SimpleCounter<uint64_t>> counter_opcode_;
   generic::SimpleCounter<uint64_t> counter_num_instructions_;
   generic::SimpleCounter<uint64_t> counter_num_cycles_;
+  // Placeholders for the counters required for the Zihpm extension.
+  std::vector<generic::SimpleCounter<uint64_t>> counter_hardware_perf_;
   // Counter used for profiling by connecting it to a profiler. This allows
   // the pc to be written to the counter, and the profiling can be enabled/
   // disabled with the other counters.
diff --git a/riscv/test/BUILD b/riscv/test/BUILD
index 3d0b397..fc60503 100644
--- a/riscv/test/BUILD
+++ b/riscv/test/BUILD
@@ -107,6 +107,7 @@
     deps = [
         "//riscv:riscv_state",
         "@com_google_absl//absl/log:check",
+        "@com_google_absl//absl/strings",
         "@com_google_googletest//:gtest_main",
         "@com_google_mpact-sim//mpact/sim/util/memory",
     ],
diff --git a/riscv/test/riscv_state_test.cc b/riscv/test/riscv_state_test.cc
index 26fca24..23e2dcc 100644
--- a/riscv/test/riscv_state_test.cc
+++ b/riscv/test/riscv_state_test.cc
@@ -16,16 +16,21 @@
 
 #include <cstdint>
 #include <iostream>
+#include <memory>
 #include <ostream>
 #include <string>
 
 #include "absl/log/check.h"
+#include "absl/strings/str_cat.h"
 #include "googlemock/include/gmock/gmock.h"
 #include "mpact/sim/util/memory/flat_demand_memory.h"
+#include "riscv/riscv_csr.h"
 #include "riscv/riscv_register.h"
 
 namespace {
 
+using ::mpact::sim::riscv::RiscVCsrEnum;
+using ::mpact::sim::riscv::RiscVCsrInterface;
 using ::mpact::sim::riscv::RiscVState;
 using ::mpact::sim::riscv::RiscVXlen;
 using ::mpact::sim::riscv::RV32Register;
@@ -40,7 +45,7 @@
 
 TEST(RiscVStateTest, Basic) {
   FlatDemandMemory memory;
-  auto* state = new RiscVState("test", RiscVXlen::RV32, &memory);
+  auto state = std::make_unique<RiscVState>("test", RiscVXlen::RV32, &memory);
   // Make sure pc has been created.
   auto iter = state->registers()->find("pc");
   auto* ptr = (iter != state->registers()->end()) ? iter->second : nullptr;
@@ -50,12 +55,11 @@
   pc->data_buffer()->Set<uint32_t>(0, kPcValue);
   auto* pc_op = state->pc_operand();
   EXPECT_EQ(pc_op->AsUint32(0), kPcValue);
-  delete state;
 }
 
 TEST(RiscVStateTest, Memory) {
   FlatDemandMemory memory;
-  auto* state = new RiscVState("test", RiscVXlen::RV32, &memory);
+  auto state = std::make_unique<RiscVState>("test", RiscVXlen::RV32, &memory);
   auto* db = state->db_factory()->Allocate<uint32_t>(1);
   state->LoadMemory(nullptr, kMemAddr, db, nullptr, nullptr);
   EXPECT_EQ(db->Get<uint32_t>(0), 0);
@@ -65,12 +69,11 @@
   state->LoadMemory(nullptr, kMemAddr, db, nullptr, nullptr);
   EXPECT_EQ(db->Get<uint32_t>(0), kMemValue);
   db->DecRef();
-  delete state;
 }
 
 TEST(RiscVStateTest, OutOfBoundLoad) {
   FlatDemandMemory memory;
-  auto* state = new RiscVState("test", RiscVXlen::RV32, &memory);
+  auto state = std::make_unique<RiscVState>("test", RiscVXlen::RV32, &memory);
   state->set_max_physical_address(kMemAddr - 4);
   state->set_on_trap([](bool is_interrupt, uint64_t trap_value,
                         uint64_t exception_code, uint64_t epc,
@@ -93,7 +96,67 @@
   EXPECT_THAT(stderr, testing::HasSubstr("Load Access Fault"));
   db->DecRef();
   dummy_inst->DecRef();
-  delete state;
+}
+
+TEST(RiscVStateTest, PerfCounterCsrNameAndIndexMatch_hpm) {
+  FlatDemandMemory memory;
+  auto state = std::make_unique<RiscVState>("test", RiscVXlen::RV32, &memory);
+
+  uint32_t hpmcounter_base = static_cast<uint32_t>(RiscVCsrEnum::kCycle);
+  for (int i = 3; i < 32; i++) {
+    ASSERT_OK_AND_ASSIGN(
+        RiscVCsrInterface * csr_by_name,
+        state->csr_set()->GetCsr(absl::StrCat("hpmcounter", i)));
+    ASSERT_OK_AND_ASSIGN(RiscVCsrInterface * csr_by_index,
+                         state->csr_set()->GetCsr(hpmcounter_base + i));
+    EXPECT_EQ(csr_by_name, csr_by_index);
+  }
+}
+
+TEST(RiscVStateTest, PerfCounterCsrNameAndIndexMatch_hpm_high) {
+  FlatDemandMemory memory;
+  auto state = std::make_unique<RiscVState>("test", RiscVXlen::RV32, &memory);
+
+  uint32_t hpmcounter_base_high = static_cast<uint32_t>(RiscVCsrEnum::kCycleH);
+  for (int i = 3; i < 32; i++) {
+    ASSERT_OK_AND_ASSIGN(
+        RiscVCsrInterface * csr_by_name,
+        state->csr_set()->GetCsr(absl::StrCat("hpmcounter", i, "h")));
+    ASSERT_OK_AND_ASSIGN(RiscVCsrInterface * csr_by_index,
+                         state->csr_set()->GetCsr(hpmcounter_base_high + i));
+    EXPECT_EQ(csr_by_name, csr_by_index);
+  }
+}
+
+TEST(RiscVStateTest, PerfCounterCsrNameAndIndexMatch_mhpm) {
+  FlatDemandMemory memory;
+  auto state = std::make_unique<RiscVState>("test", RiscVXlen::RV32, &memory);
+
+  uint32_t mhpmcounter_base = static_cast<uint32_t>(RiscVCsrEnum::kMCycle);
+  for (int i = 3; i < 32; i++) {
+    ASSERT_OK_AND_ASSIGN(
+        RiscVCsrInterface * csr_by_name,
+        state->csr_set()->GetCsr(absl::StrCat("mhpmcounter", i)));
+    ASSERT_OK_AND_ASSIGN(RiscVCsrInterface * csr_by_index,
+                         state->csr_set()->GetCsr(mhpmcounter_base + i));
+    EXPECT_EQ(csr_by_name, csr_by_index);
+  }
+}
+
+TEST(RiscVStateTest, PerfCounterCsrNameAndIndexMatch_mhpm_high) {
+  FlatDemandMemory memory;
+  auto state = std::make_unique<RiscVState>("test", RiscVXlen::RV32, &memory);
+
+  uint32_t mhpmcounter_base_high =
+      static_cast<uint32_t>(RiscVCsrEnum::kMCycleH);
+  for (int i = 3; i < 32; i++) {
+    ASSERT_OK_AND_ASSIGN(
+        RiscVCsrInterface * csr_by_name,
+        state->csr_set()->GetCsr(absl::StrCat("mhpmcounter", i, "h")));
+    ASSERT_OK_AND_ASSIGN(RiscVCsrInterface * csr_by_index,
+                         state->csr_set()->GetCsr(mhpmcounter_base_high + i));
+    EXPECT_EQ(csr_by_name, csr_by_index);
+  }
 }
 
 }  // namespace
diff --git a/riscv/test/riscv_top_test.cc b/riscv/test/riscv_top_test.cc
index a5d3709..058b6d2 100644
--- a/riscv/test/riscv_top_test.cc
+++ b/riscv/test/riscv_top_test.cc
@@ -16,10 +16,12 @@
 
 #include <cstdint>
 #include <string>
+#include <vector>
 
 #include "absl/log/check.h"
 #include "absl/status/status.h"
 #include "absl/strings/str_cat.h"
+#include "absl/strings/str_join.h"
 #include "googlemock/include/gmock/gmock.h"
 #include "mpact/sim/generic/core_debug_interface.h"
 #include "mpact/sim/generic/decoder_interface.h"
@@ -590,6 +592,39 @@
   EXPECT_NE(riscv_top_->ReadRegister("cycle").value(), 0);
   EXPECT_NE(riscv_top_->ReadRegister("time").value(), 0);
   EXPECT_NE(riscv_top_->ReadRegister("instret").value(), 0);
+
+  // Verify that the hardware perf counters are initialized. These checks are
+  // based on coverage gaps found in mutation testing.
+  std::vector<int> uninitialized_perf_counter_indices;
+  for (int i = 0; i < riscv_top_->counter_hardware_perf()->size(); ++i) {
+    if (!riscv_top_->counter_hardware_perf()->at(i).IsInitialized()) {
+      uninitialized_perf_counter_indices.push_back(i);
+    }
+  }
+  EXPECT_EQ(uninitialized_perf_counter_indices.size(), 0)
+      << "Uninitialized perf counters: "
+      << absl::StrJoin(uninitialized_perf_counter_indices, ",");
+
+  // Verify that the hardware perf counters are connected to the CSRs.
+  // This is based on a mutation testing finding.
+  // Get the CSR value, increment the underlying counter, and verify that the
+  // CSR value changes.
+  std::vector<std::string> failed_perf_counter_csr_names;
+  for (int i = 0; i < riscv_top_->counter_hardware_perf()->size(); ++i) {
+    std::string csr_name = absl::StrCat("hpmcounter", i + 3);
+    std::string machine_csr_name = absl::StrCat("mhpmcounter", i + 3);
+    auto csr_value = riscv_top_->ReadRegister(csr_name).value();
+    riscv_top_->counter_hardware_perf()->at(i).Increment(1);
+    if (riscv_top_->ReadRegister(csr_name).value() == csr_value) {
+      failed_perf_counter_csr_names.push_back(csr_name);
+    }
+    if (riscv_top_->ReadRegister(machine_csr_name).value() == csr_value) {
+      failed_perf_counter_csr_names.push_back(machine_csr_name);
+    }
+  }
+  EXPECT_EQ(failed_perf_counter_csr_names.size(), 0)
+      << "Failed CSR names: "
+      << absl::StrJoin(failed_perf_counter_csr_names, ",");
 }
 
 }  // namespace