Set maximum physical memory of Kelvin simulation

kelvin HW only has 4MB memory. Set the memory size in the simulator so it can catch the illegal memory access.

PiperOrigin-RevId: 550580376
diff --git a/WORKSPACE b/WORKSPACE
index d40c02d..50975dd 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -6,9 +6,9 @@
 # MPACT-RiscV repo
 http_archive(
     name = "com_google_mpact-riscv",
-    sha256 = "b2b3ce1354d2da48ee195b72b682ecffeb0844b1fbd4f8e83a429d8b21194e61",
-    strip_prefix = "mpact-riscv-a21d14041b9f034519e2f9fa359f2902b433cf23",
-    url = "https://github.com/google/mpact-riscv/archive/a21d14041b9f034519e2f9fa359f2902b433cf23.tar.gz",
+    sha256 = "de8672c59d454182a406fe859de4c87f86116dac320ae366aca60a3258370c1b",
+    strip_prefix = "mpact-riscv-eadd26c36777070935ae85ff7f556185085dc188",
+    url = "https://github.com/google/mpact-riscv/archive/eadd26c36777070935ae85ff7f556185085dc188.tar.gz",
 )
 
 # MPACT-Sim repo
diff --git a/sim/kelvin_top.cc b/sim/kelvin_top.cc
index ffcd3c3..5cd361c 100644
--- a/sim/kelvin_top.cc
+++ b/sim/kelvin_top.cc
@@ -78,6 +78,7 @@
   // Create the simulation state
   state_ = new sim::KelvinState(kKelvinName, mpact::sim::riscv::RiscVXlen::RV32,
                                 memory_);
+  state_->set_max_physical_address(kKelvinMaxMemoryAddress);
   fp_state_ = new mpact::sim::riscv::RiscVFPState(state_);
   state_->set_rv_fp(fp_state_);
   pc_ = state_->registers()->at(sim::KelvinState::kPcName);
@@ -143,18 +144,35 @@
         return false;
       });
 
-  // Set illegal instruction callback.
+  // Set trap callbacks.
   state_->set_on_trap([this](bool is_interrupt, uint64_t trap_value,
                              uint64_t exception_code, uint64_t epc,
                              const Instruction *inst) -> bool {
-    if (exception_code ==
-        static_cast<uint64_t>(
-            mpact::sim::riscv::ExceptionCode::kIllegalInstruction)) {
-      std::cerr << "Illegal instruction at 0x" << std::hex << epc << std::endl;
-      RequestHalt(HaltReason::kUserRequest, nullptr);
-      return true;
+    auto code = static_cast<mpact::sim::riscv::ExceptionCode>(exception_code);
+    bool result = false;
+    switch (code) {
+      case mpact::sim::riscv::ExceptionCode::kIllegalInstruction: {
+        std::cerr << "Illegal instruction at 0x" << std::hex << epc
+                  << std::endl;
+        RequestHalt(HaltReason::kUserRequest, nullptr);
+        result = true;
+      } break;
+      case mpact::sim::riscv::ExceptionCode::kLoadAccessFault: {
+        std::cerr << "Memory load access fault at 0x" << std::hex << epc
+                  << " as: " << inst->AsString() << std::endl;
+        RequestHalt(HaltReason::kUserRequest, nullptr);
+        result = true;
+      } break;
+      case mpact::sim::riscv::ExceptionCode::kStoreAccessFault: {
+        std::cerr << "Memory store access fault at 0x" << std::hex << epc
+                  << " as: " << inst->AsString() << std::endl;
+        RequestHalt(HaltReason::kUserRequest, nullptr);
+        result = true;
+      } break;
+      default:
+        break;
     }
-    return false;
+    return result;
   });
 
   semihost_->set_exit_callback(
diff --git a/sim/kelvin_top.h b/sim/kelvin_top.h
index e562e3b..206623b 100644
--- a/sim/kelvin_top.h
+++ b/sim/kelvin_top.h
@@ -23,6 +23,8 @@
 
 namespace kelvin::sim {
 
+constexpr uint64_t kKelvinMaxMemoryAddress = 0x3f'ffffULL;  // 4MB
+
 // Top level class for the Kelvin simulator. This is the main interface for
 // interacting and controlling execution of programs running on the simulator.
 // This class brings together the decoder, the architecture state, and control.
diff --git a/sim/test/kelvin_top_test.cc b/sim/test/kelvin_top_test.cc
index 28e4b8b..84edeb5 100644
--- a/sim/test/kelvin_top_test.cc
+++ b/sim/test/kelvin_top_test.cc
@@ -34,6 +34,9 @@
 // The depot path to the test directory.
 constexpr char kDepotPath[] = "sim/test/";
 
+// Maximum memory size used by riscv programs build for userspace.
+constexpr uint64_t kRiscv32MaxAddress = 0x3'ffff'ffffULL;
+
 class KelvinTopTest : public testing::Test {
  protected:
   KelvinTopTest() {
@@ -63,8 +66,30 @@
   FlatDemandMemory *memory_ = nullptr;
 };
 
-// Runs the program from beginning to end.
-TEST_F(KelvinTopTest, RunHelloProgram) {
+// Check the max memory size
+TEST_F(KelvinTopTest, CheckDefaultMaxMemorySize) {
+  EXPECT_EQ(kelvin_top_->state()->max_physical_address(),
+            kelvin::sim::kKelvinMaxMemoryAddress);
+}
+
+// Run a program exceeds the default memory setting
+TEST_F(KelvinTopTest, RunProgramExceedDefaultMemory) {
+  LoadFile(kRV32imfElfFileName);
+  testing::internal::CaptureStderr();
+  EXPECT_OK(kelvin_top_->WriteRegister("pc", entry_point_));
+  EXPECT_OK(kelvin_top_->Run());
+  EXPECT_OK(kelvin_top_->Wait());
+  auto halt_result = kelvin_top_->GetLastHaltReason();
+  CHECK_OK(halt_result);
+  EXPECT_EQ(static_cast<int>(halt_result.value()),
+            static_cast<int>(HaltReason::kUserRequest));
+  const std::string stderr_str = testing::internal::GetCapturedStderr();
+  EXPECT_THAT(stderr_str, testing::HasSubstr("Memory store access fault"));
+}
+
+// Runs the program from has ebreak (from syscall).
+TEST_F(KelvinTopTest, RunEbreakProgram) {
+  kelvin_top_->state()->set_max_physical_address(kRiscv32MaxAddress);
   LoadFile(kRV32imfElfFileName);
   testing::internal::CaptureStdout();
   EXPECT_OK(kelvin_top_->WriteRegister("pc", entry_point_));
@@ -81,6 +106,7 @@
 // Runs the program from beginning to end. Enable arm semihosting.
 TEST_F(KelvinTopTest, RunHelloProgramSemihost) {
   absl::SetFlag(&FLAGS_use_semihost, true);
+  kelvin_top_->state()->set_max_physical_address(kRiscv32MaxAddress);
   LoadFile(kRV32imfElfFileName);
   testing::internal::CaptureStdout();
   EXPECT_OK(kelvin_top_->WriteRegister("pc", entry_point_));
@@ -113,6 +139,7 @@
 // Runs the rv32i program with arm semihosting.
 TEST_F(KelvinTopTest, RunRV32IProgram) {
   absl::SetFlag(&FLAGS_use_semihost, true);
+  kelvin_top_->state()->set_max_physical_address(kRiscv32MaxAddress);
   LoadFile(kRV32iElfFileName);
   testing::internal::CaptureStdout();
   EXPECT_OK(kelvin_top_->WriteRegister("pc", entry_point_));
@@ -130,6 +157,7 @@
 // Runs the rv32m program with arm semihosting.
 TEST_F(KelvinTopTest, RunRV32MProgram) {
   absl::SetFlag(&FLAGS_use_semihost, true);
+  kelvin_top_->state()->set_max_physical_address(kRiscv32MaxAddress);
   LoadFile(kRV32mElfFileName);
   testing::internal::CaptureStdout();
   EXPECT_OK(kelvin_top_->WriteRegister("pc", entry_point_));
@@ -147,6 +175,7 @@
 // Runs the rv32 soft float program with arm semihosting.
 TEST_F(KelvinTopTest, RunRV32SoftFProgram) {
   absl::SetFlag(&FLAGS_use_semihost, true);
+  kelvin_top_->state()->set_max_physical_address(kRiscv32MaxAddress);
   LoadFile(kRV32SoftFloatElfFileName);
   testing::internal::CaptureStdout();
   EXPECT_OK(kelvin_top_->WriteRegister("pc", entry_point_));
@@ -180,6 +209,7 @@
 // Steps through the program from beginning to end.
 TEST_F(KelvinTopTest, StepProgram) {
   LoadFile(kRV32imfElfFileName);
+  kelvin_top_->state()->set_max_physical_address(kRiscv32MaxAddress);
   testing::internal::CaptureStdout();
   EXPECT_OK(kelvin_top_->WriteRegister("pc", entry_point_));
 
@@ -197,6 +227,7 @@
 // Sets/Clears breakpoints without executing the program.
 TEST_F(KelvinTopTest, SetAndClearBreakpoint) {
   LoadFile(kRV32imfElfFileName);
+  kelvin_top_->state()->set_max_physical_address(kRiscv32MaxAddress);
   auto result = loader_->GetSymbol("printf");
   EXPECT_OK(result);
   auto address = result.value().first;
@@ -217,6 +248,7 @@
 // Runs program with breakpoint at printf with arm semihosting.
 TEST_F(KelvinTopTest, RunWithBreakpoint) {
   absl::SetFlag(&FLAGS_use_semihost, true);
+  kelvin_top_->state()->set_max_physical_address(kRiscv32MaxAddress);
   LoadFile(kRV32imfElfFileName);
 
   // Set breakpoint at printf.