Add custom `HaltAbort` reason for `ebreak` termination.

PiperOrigin-RevId: 563617193
diff --git a/sim/kelvin_top.cc b/sim/kelvin_top.cc
index a18cc08..5ec7fa9 100644
--- a/sim/kelvin_top.cc
+++ b/sim/kelvin_top.cc
@@ -150,7 +150,7 @@
             RequestHalt(HaltReason::kSoftwareBreakpoint, inst);
           } else {  // The default Kelvin simulation mode.
             std::cout << "Program exits with fault" << std::endl;
-            RequestHalt(HaltReason::kUserRequest, inst);
+            RequestHalt(kHaltAbort, inst);
           }
           return true;
         }
diff --git a/sim/kelvin_top.h b/sim/kelvin_top.h
index 9c28e30..3f8aace 100644
--- a/sim/kelvin_top.h
+++ b/sim/kelvin_top.h
@@ -30,6 +30,12 @@
 namespace kelvin::sim {
 
 using ::mpact::sim::generic::DataBuffer;
+using HaltReason = mpact::sim::generic::CoreDebugInterface::HaltReason;
+using HaltReasonValueType =
+    mpact::sim::generic::CoreDebugInterface::HaltReasonValueType;
+
+// Custom HaltReason for `ebreak`
+const HaltReasonValueType kHaltAbort = *HaltReason::kUserSpecifiedMin + 1;
 
 // Top level class for the Kelvin simulator. This is the main interface for
 // interacting and controlling execution of programs running on the simulator.
@@ -38,9 +44,6 @@
                   public mpact::sim::generic::CoreDebugInterface {
  public:
   using RunStatus = mpact::sim::generic::CoreDebugInterface::RunStatus;
-  using HaltReason = mpact::sim::generic::CoreDebugInterface::HaltReason;
-  using HaltReasonValueType =
-      mpact::sim::generic::CoreDebugInterface::HaltReasonValueType;
 
   explicit KelvinTop(std::string name);
   ~KelvinTop() override;
diff --git a/sim/renode/BUILD b/sim/renode/BUILD
index c53af89..a215a01 100644
--- a/sim/renode/BUILD
+++ b/sim/renode/BUILD
@@ -14,6 +14,7 @@
     ],
     deps = [
         ":renode_debug_interface",
+        "//sim:kelvin_top",
         "@com_google_absl//absl/container:flat_hash_map",
         "@com_google_absl//absl/log",
         "@com_google_absl//absl/strings",
diff --git a/sim/renode/renode_mpact.cc b/sim/renode/renode_mpact.cc
index f8a796d..3c25082 100644
--- a/sim/renode/renode_mpact.cc
+++ b/sim/renode/renode_mpact.cc
@@ -4,6 +4,7 @@
 #include <limits>
 #include <string>
 
+#include "sim/kelvin_top.h"
 #include "sim/renode/renode_debug_interface.h"
 #include "absl/log/log.h"
 #include "absl/strings/str_cat.h"
@@ -242,7 +243,6 @@
   }
   // If the previous halt reason was a semihost halt request, then we shouldn't
   // step any further. Just return with "waiting for interrupt" code.
-  using HaltReason = RenodeDebugInterface::HaltReason;
   using mpact::sim::generic::operator*;  // NOLINT: used below.
   if (halt_res.value() == *HaltReason::kSemihostHaltRequest) {
     if (status != nullptr) {
@@ -288,6 +288,12 @@
         }
         return total_executed;
         break;
+      case kHaltAbort:  // `ebreak` custom halt reason
+        if (status != nullptr) {
+          *status = static_cast<int32_t>(ExecutionResult::kAborted);
+        }
+        return total_executed;
+        break;
       default:
         break;
     }
@@ -333,7 +339,6 @@
     return -1;
   }
   // Map the halt status appropriately.
-  using HaltReason = RenodeDebugInterface::HaltReason;
   using mpact::sim::generic::operator*;  // NOLINT: used below.
   if (status != nullptr) {
     switch (halt_res.value()) {
@@ -349,6 +354,9 @@
       case *HaltReason::kNone:
         *status = static_cast<int32_t>(ExecutionResult::kOk);
         break;
+      case kelvin::sim::kHaltAbort:
+        *status = static_cast<int32_t>(ExecutionResult::kAborted);
+        break;
       default:
         break;
     }
diff --git a/sim/renode/test/BUILD b/sim/renode/test/BUILD
index 4b1d191..b1d3ac2 100644
--- a/sim/renode/test/BUILD
+++ b/sim/renode/test/BUILD
@@ -8,6 +8,7 @@
     data = [
         "//sim/test:testfiles/hello_world_mpause.bin",
         "//sim/test:testfiles/hello_world_mpause.elf",
+        "//sim/test:testfiles/kelvin_ebreak.elf",
     ],
     deps = [
         "//sim/renode:kelvin_renode",
@@ -26,6 +27,7 @@
     data = [
         "//sim/test:testfiles/hello_world_mpause.bin",
         "//sim/test:testfiles/hello_world_mpause.elf",
+        "//sim/test:testfiles/kelvin_ebreak.elf",
     ],
     deps = [
         "//sim:kelvin_top",
diff --git a/sim/renode/test/kelvin_renode_test.cc b/sim/renode/test/kelvin_renode_test.cc
index 397dcf6..a64c689 100644
--- a/sim/renode/test/kelvin_renode_test.cc
+++ b/sim/renode/test/kelvin_renode_test.cc
@@ -19,6 +19,7 @@
 
 constexpr char kFileName[] = "hello_world_mpause.elf";
 constexpr char kBinFileName[] = "hello_world_mpause.bin";
+constexpr char kEbreakFileName[] = "kelvin_ebreak.elf";
 // The depot path to the test directory.
 constexpr char kDepotPath[] = "sim/test/";
 constexpr char kTopName[] = "test";
@@ -76,6 +77,30 @@
   delete loader;
 }
 
+TEST_F(KelvinRenodeTest, RunEbreakElfProgram) {
+  std::string file_name =
+      absl::StrCat(kDepotPath, "testfiles/", kEbreakFileName);
+  // Load the program.
+  auto *loader = new mpact::sim::util::ElfProgramLoader(top_);
+  auto result = loader->LoadProgram(file_name);
+  CHECK_OK(result);
+  auto entry_point = result.value();
+  // Run the program.
+  testing::internal::CaptureStdout();
+  EXPECT_TRUE(top_->WriteRegister("pc", entry_point).ok());
+  EXPECT_TRUE(top_->Run().ok());
+  EXPECT_TRUE(top_->Wait().ok());
+  // Check the results.
+  auto halt_result = top_->GetLastHaltReason();
+  CHECK_OK(halt_result);
+  EXPECT_EQ(static_cast<int>(halt_result.value()),
+            static_cast<int>(kelvin::sim::kHaltAbort));
+  const std::string stdout_str = testing::internal::GetCapturedStdout();
+  EXPECT_EQ("Program exits with fault\n", stdout_str);
+  // Clean up.
+  delete loader;
+}
+
 TEST_F(KelvinRenodeTest, RunBinProgram) {
   std::string file_name = absl::StrCat(kDepotPath, "testfiles/", kBinFileName);
   constexpr uint64_t kBinFileAddress = 0x0;
diff --git a/sim/renode/test/renode_mpact_test.cc b/sim/renode/test/renode_mpact_test.cc
index d639225..5eda348 100644
--- a/sim/renode/test/renode_mpact_test.cc
+++ b/sim/renode/test/renode_mpact_test.cc
@@ -18,6 +18,7 @@
 using kelvin::sim::renode::ExecutionResult;
 using mpact::sim::riscv::DebugRegisterEnum;
 
+constexpr char kEbreakExecutableFileName[] = "kelvin_ebreak.elf";
 constexpr char kExecutableFileName[] = "hello_world_mpause.elf";
 constexpr char kBinFileName[] = "hello_world_mpause.bin";
 constexpr uint64_t kBinFileAddress = 0x0;
@@ -196,6 +197,27 @@
   EXPECT_EQ("Program exits properly\n", stdout_str);
 }
 
+TEST_F(RenodeMpactTest, StepEbreakExecutableProgram) {
+  testing::internal::CaptureStdout();
+  const std::string input_file_name =
+      absl::StrCat(kDepotPath, "testfiles/", kEbreakExecutableFileName);
+  int32_t status;
+  (void)load_executable(sim_id_, input_file_name.c_str(), &status);
+  CHECK_EQ(status, 0);
+  constexpr uint64_t kStepCount = 500;
+  uint64_t count;
+  while (true) {
+    count = step(sim_id_, kStepCount, &status);
+    if (count != kStepCount) break;
+    EXPECT_EQ(status, static_cast<int32_t>(ExecutionResult::kOk));
+  }
+  // Execution should now have completed and the program has printed the fault
+  // exit message.
+  EXPECT_EQ(status, static_cast<int32_t>(ExecutionResult::kAborted));
+  const std::string stdout_str = testing::internal::GetCapturedStdout();
+  EXPECT_EQ("Program exits with fault\n", stdout_str);
+}
+
 TEST_F(RenodeMpactTest, StepImageProgram) {
   const std::string input_file_name =
       absl::StrCat(kDepotPath, "testfiles/", kBinFileName);
diff --git a/sim/test/BUILD b/sim/test/BUILD
index 58b2fa5..7db1c7b 100644
--- a/sim/test/BUILD
+++ b/sim/test/BUILD
@@ -5,6 +5,7 @@
         "testfiles/hello_world_mpause.bin",
         "testfiles/hello_world_mpause.elf",
         "testfiles/hello_world_rv32imf.elf",
+        "testfiles/kelvin_ebreak.elf",
         "testfiles/kelvin_vldvst.elf",
         "testfiles/rv32i.elf",
         "testfiles/rv32m.elf",
@@ -62,6 +63,7 @@
         "testfiles/hello_world_mpause.bin",
         "testfiles/hello_world_mpause.elf",
         "testfiles/hello_world_rv32imf.elf",
+        "testfiles/kelvin_ebreak.elf",
         "testfiles/kelvin_vldvst.elf",
         "testfiles/rv32i.elf",
         "testfiles/rv32m.elf",
diff --git a/sim/test/kelvin_top_test.cc b/sim/test/kelvin_top_test.cc
index e5ab21c..85d2491 100644
--- a/sim/test/kelvin_top_test.cc
+++ b/sim/test/kelvin_top_test.cc
@@ -26,6 +26,7 @@
 using ::mpact::sim::util::FlatDemandMemory;
 
 using HaltReason = ::mpact::sim::generic::CoreDebugInterface::HaltReason;
+constexpr char kEbreakElfFileName[] = "kelvin_ebreak.elf";
 constexpr char kMpauseBinaryFileName[] = "hello_world_mpause.bin";
 constexpr char kMpauseElfFileName[] = "hello_world_mpause.elf";
 constexpr char kRV32imfElfFileName[] = "hello_world_rv32imf.elf";
@@ -95,16 +96,14 @@
 
 // Runs the program from has ebreak (from syscall).
 TEST_F(KelvinTopTest, RunEbreakProgram) {
-  kelvin_top_->state()->set_max_physical_address(kRiscv32MaxAddress);
-  LoadFile(kRV32imfElfFileName);
+  LoadFile(kEbreakElfFileName);
   testing::internal::CaptureStdout();
   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));
+  EXPECT_EQ(halt_result.value(), kelvin::sim::kHaltAbort);
   const std::string stdout_str = testing::internal::GetCapturedStdout();
   EXPECT_EQ("Program exits with fault\n", stdout_str);
 }
@@ -289,8 +288,7 @@
 
 // Steps through the program from beginning to end.
 TEST_F(KelvinTopTest, StepProgram) {
-  LoadFile(kRV32imfElfFileName);
-  kelvin_top_->state()->set_max_physical_address(kRiscv32MaxAddress);
+  LoadFile(kEbreakElfFileName);
   testing::internal::CaptureStdout();
   EXPECT_OK(kelvin_top_->WriteRegister("pc", entry_point_));
 
@@ -298,8 +296,7 @@
   EXPECT_OK(res.status());
   auto halt_result = kelvin_top_->GetLastHaltReason();
   CHECK_OK(halt_result);
-  EXPECT_EQ(static_cast<int>(halt_result.value()),
-            static_cast<int>(HaltReason::kUserRequest));
+  EXPECT_EQ(halt_result.value(), kelvin::sim::kHaltAbort);
 
   EXPECT_EQ("Program exits with fault\n",
             testing::internal::GetCapturedStdout());
diff --git a/sim/test/testfiles/kelvin_ebreak.elf b/sim/test/testfiles/kelvin_ebreak.elf
new file mode 100644
index 0000000..5931229
--- /dev/null
+++ b/sim/test/testfiles/kelvin_ebreak.elf
Binary files differ