This adds a cache class that can be used as part of the memory hierarchy.

As an example, an instruction cache is added (with an optional flag) to
mpact_riscv and mpact_cheriot.

PiperOrigin-RevId: 678760853
Change-Id: Iccbe311bf7478ee79b38996d35b7101a45ae304f
diff --git a/cheriot/BUILD b/cheriot/BUILD
index a08047b..87dd61c 100644
--- a/cheriot/BUILD
+++ b/cheriot/BUILD
@@ -491,6 +491,7 @@
         ":cheriot_state",
         ":riscv_cheriot_isa",
         "@com_google_absl//absl/container:flat_hash_map",
+        "@com_google_absl//absl/flags:flag",
         "@com_google_absl//absl/functional:any_invocable",
         "@com_google_absl//absl/functional:bind_front",
         "@com_google_absl//absl/log",
@@ -511,6 +512,7 @@
         "@com_google_mpact-sim//mpact/sim/generic:decode_cache",
         "@com_google_mpact-sim//mpact/sim/generic:type_helpers",
         "@com_google_mpact-sim//mpact/sim/util/memory",
+        "@com_google_mpact-sim//mpact/sim/util/memory:cache",
         "@com_googlesource_code_re2//:re2",
     ],
 )
diff --git a/cheriot/cheriot_top.cc b/cheriot/cheriot_top.cc
index 6df56ad..9c663d0 100644
--- a/cheriot/cheriot_top.cc
+++ b/cheriot/cheriot_top.cc
@@ -18,9 +18,10 @@
 #include <cstdint>
 #include <cstring>
 #include <string>
-#include <thread>
+#include <thread>  // NOLINT: third party code.
 #include <utility>
 
+#include "absl/flags/flag.h"
 #include "absl/functional/any_invocable.h"
 #include "absl/functional/bind_front.h"
 #include "absl/log/check.h"
@@ -43,6 +44,7 @@
 #include "mpact/sim/generic/decode_cache.h"
 #include "mpact/sim/generic/type_helpers.h"
 #include "mpact/sim/util/memory/atomic_memory.h"
+#include "mpact/sim/util/memory/cache.h"
 #include "mpact/sim/util/memory/memory_interface.h"
 #include "mpact/sim/util/memory/memory_watcher.h"
 #include "mpact/sim/util/memory/tagged_flat_demand_memory.h"
@@ -53,6 +55,9 @@
 #include "riscv//riscv_csr.h"
 #include "riscv//riscv_register.h"
 
+// Flag to enable and configure instruction cache.
+ABSL_FLAG(std::string, icache, "", "Instruction cache configuration");
+
 namespace mpact {
 namespace sim {
 namespace cheriot {
@@ -60,6 +65,7 @@
 using ::mpact::sim::generic::ActionPointManagerBase;
 using ::mpact::sim::generic::BreakpointManager;
 using ::mpact::sim::riscv::RiscVActionPointMemoryInterface;
+using ::mpact::sim::util::Cache;
 using EC = ::mpact::sim::cheriot::ExceptionCode;
 using PB = ::mpact::sim::cheriot::CheriotRegister::PermissionBits;
 
@@ -88,6 +94,8 @@
 
   if (branch_trace_db_ != nullptr) branch_trace_db_->DecRef();
 
+  delete icache_;
+  if (inst_db_) inst_db_->DecRef();
   delete rv_bp_manager_;
   delete cheriot_decode_cache_;
   delete atomic_memory_;
@@ -144,6 +152,16 @@
     }
     return false;
   });
+  // Instruction cache.
+  if (!absl::GetFlag(FLAGS_icache).empty()) {
+    icache_ = new Cache("icache", this);
+    absl::Status status =
+        icache_->Configure(absl::GetFlag(FLAGS_icache), &counter_num_cycles_);
+    if (!status.ok()) {
+      LOG(ERROR) << "Failed to configure instruction cache: " << status;
+    }
+    inst_db_ = state_->db_factory()->Allocate<uint32_t>(1);
+  }
 
   // Make sure the architectural and abi register aliases are added.
   std::string reg_name;
@@ -235,6 +253,7 @@
   real_inst->IncRef();
   uint64_t next_pc = pc + real_inst->size();
   bool executed = false;
+  if (icache_) ICacheFetch(pc);
   do {
     executed = ExecuteInstruction(real_inst);
     counter_num_cycles_.Increment(1);
@@ -303,6 +322,7 @@
     // Set the next_pc to the next sequential instruction.
     next_pc = pc + inst->size();
     bool executed = false;
+    if (icache_) ICacheFetch(pc);
     do {
       executed = ExecuteInstruction(inst);
       counter_num_cycles_.Increment(1);
@@ -405,6 +425,7 @@
       // executed will overwrite this.
       next_pc = pc + inst->size();
       bool executed = false;
+      if (icache_) ICacheFetch(pc);
       do {
         // Try executing the instruction. If it fails, advance a cycle
         // and try again.
@@ -1015,6 +1036,10 @@
   }
 }
 
+void CheriotTop::ICacheFetch(uint64_t address) {
+  icache_->Load(address, inst_db_, nullptr, nullptr);
+}
+
 }  // namespace cheriot
 }  // namespace sim
 }  // namespace mpact
diff --git a/cheriot/cheriot_top.h b/cheriot/cheriot_top.h
index 655bc94..e187fac 100644
--- a/cheriot/cheriot_top.h
+++ b/cheriot/cheriot_top.h
@@ -38,6 +38,7 @@
 #include "mpact/sim/generic/data_buffer.h"
 #include "mpact/sim/generic/decode_cache.h"
 #include "mpact/sim/generic/decoder_interface.h"
+#include "mpact/sim/util/memory/cache.h"
 #include "mpact/sim/util/memory/memory_interface.h"
 #include "mpact/sim/util/memory/memory_watcher.h"
 #include "mpact/sim/util/memory/tagged_memory_watcher.h"
@@ -52,6 +53,7 @@
 using ::mpact::sim::generic::BreakpointManager;
 using ::mpact::sim::generic::DecoderInterface;
 using ::mpact::sim::riscv::RiscVActionPointMemoryInterface;
+using ::mpact::sim::util::Cache;
 
 struct BranchTraceEntry {
   uint32_t from;
@@ -160,6 +162,7 @@
   absl::Status StepPastBreakpoint();
   // Set the pc value.
   void SetPc(uint64_t value);
+  void ICacheFetch(uint64_t address);
   // Branch tracing.
   void AddToBranchTrace(uint64_t from, uint64_t to);
   // The DB factory is used to manage data buffers for memory read/writes.
@@ -215,6 +218,9 @@
   LazyRE2 cap_reg_re_;
   // Flag for breaking on a control flow change.
   bool break_on_control_flow_change_ = false;
+  // ICache.
+  Cache *icache_ = nullptr;
+  DataBuffer *inst_db_ = nullptr;
 };
 
 }  // namespace cheriot