Adds exceptions on instruction decoding when the C bit is not set in MISA.

Also refactors the instruction decoders to reduce code duplication.

PiperOrigin-RevId: 827555953
Change-Id: I4f5da71e4d84c4c5866291f43d6fb6406f10a84d
diff --git a/riscv/BUILD b/riscv/BUILD
index 8d7eab3..8429369 100644
--- a/riscv/BUILD
+++ b/riscv/BUILD
@@ -761,6 +761,23 @@
 )
 
 cc_library(
+    name = "riscv_generic_decoder",
+    hdrs = [
+        "riscv_generic_decoder.h",
+    ],
+    deps = [
+        ":riscv_state",
+        "@com_google_absl//absl/log",
+        "@com_google_absl//absl/status",
+        "@com_google_mpact-sim//mpact/sim/generic:core",
+        "@com_google_mpact-sim//mpact/sim/generic:instruction",
+        "@com_google_mpact-sim//mpact/sim/generic:program_error",
+        "@com_google_mpact-sim//mpact/sim/generic:type_helpers",
+        "@com_google_mpact-sim//mpact/sim/util/memory",
+    ],
+)
+
+cc_library(
     name = "riscv32g_decoder",
     srcs = [
         "riscv32_decoder.cc",
@@ -774,6 +791,7 @@
     deps = [
         ":riscv32g_bin_fmt",
         ":riscv32g_isa",
+        ":riscv_generic_decoder",
         ":riscv_state",
         "@com_google_absl//absl/container:flat_hash_map",
         "@com_google_absl//absl/functional:any_invocable",
@@ -835,6 +853,7 @@
         ":riscv32gzb_bin_fmt",
         ":riscv32gzb_isa",
         ":riscv_encoding_common",
+        ":riscv_generic_decoder",
         ":riscv_getters",
         ":riscv_state",
         "@com_google_absl//absl/log",
@@ -861,6 +880,7 @@
         ":riscv64gzb_bin_fmt",
         ":riscv64gzb_isa",
         ":riscv_encoding_common",
+        ":riscv_generic_decoder",
         ":riscv_getters",
         ":riscv_state",
         "@com_google_absl//absl/log",
@@ -886,6 +906,7 @@
     deps = [
         ":riscv32gv_bin_fmt",
         ":riscv32gv_isa",
+        ":riscv_generic_decoder",
         ":riscv_state",
         "@com_google_absl//absl/container:flat_hash_map",
         "@com_google_absl//absl/functional:any_invocable",
@@ -916,6 +937,7 @@
         ":riscv32gvzb_bin_fmt",
         ":riscv32gvzb_isa",
         ":riscv_encoding_common",
+        ":riscv_generic_decoder",
         ":riscv_getters",
         ":riscv_state",
         "@com_google_absl//absl/log",
@@ -942,6 +964,7 @@
     deps = [
         ":riscv64g_bin_fmt",
         ":riscv64g_isa",
+        ":riscv_generic_decoder",
         ":riscv_state",
         "@com_google_absl//absl/container:flat_hash_map",
         "@com_google_absl//absl/functional:any_invocable",
@@ -1015,6 +1038,7 @@
     deps = [
         ":riscv64gv_bin_fmt",
         ":riscv64gv_isa",
+        ":riscv_generic_decoder",
         ":riscv_state",
         "@com_google_absl//absl/base",
         "@com_google_absl//absl/container:flat_hash_map",
@@ -1046,6 +1070,7 @@
         ":riscv64gvzb_bin_fmt",
         ":riscv64gvzb_isa",
         ":riscv_encoding_common",
+        ":riscv_generic_decoder",
         ":riscv_getters",
         ":riscv_state",
         "@com_google_absl//absl/log",
diff --git a/riscv/riscv32_decoder.cc b/riscv/riscv32_decoder.cc
index c63a7c3..847c03e 100644
--- a/riscv/riscv32_decoder.cc
+++ b/riscv/riscv32_decoder.cc
@@ -15,90 +15,36 @@
 #include "riscv/riscv32_decoder.h"
 
 #include <cstdint>
-#include <string>
+#include <memory>
 
 #include "mpact/sim/generic/instruction.h"
-#include "mpact/sim/generic/program_error.h"
-#include "mpact/sim/generic/type_helpers.h"
 #include "mpact/sim/util/memory/memory_interface.h"
 #include "riscv/riscv32g_decoder.h"
 #include "riscv/riscv32g_encoding.h"
 #include "riscv/riscv32g_enums.h"
+#include "riscv/riscv_generic_decoder.h"
 #include "riscv/riscv_state.h"
 
 namespace mpact {
 namespace sim {
 namespace riscv {
 
-using ::mpact::sim::generic::operator*;  // NOLINT: is used below (clang error).
-
 RiscV32Decoder::RiscV32Decoder(RiscVState* state, util::MemoryInterface* memory)
     : state_(state), memory_(memory) {
-  // Get a handle to the internal error in the program error controller.
-  decode_error_ = state->program_error_controller()->GetProgramError(
-      generic::ProgramErrorController::kInternalErrorName);
-
-  // Need a data buffer to load instructions from memory. Allocate a single
-  // buffer that can be reused for each instruction word.
-  inst_db_ = state_->db_factory()->Allocate<uint32_t>(1);
   // Allocate the isa factory class, the top level isa decoder instance, and
   // the encoding parser.
-  riscv_isa_factory_ = new RV32IsaFactory();
-  riscv_isa_ = new isa32::RiscV32GInstructionSet(state, riscv_isa_factory_);
-  riscv_encoding_ = new isa32::RiscV32GEncoding(state);
-  decode_error_ = state->program_error_controller()->GetProgramError(
-      generic::ProgramErrorController::kInternalErrorName);
-}
-
-RiscV32Decoder::~RiscV32Decoder() {
-  delete riscv_isa_;
-  delete riscv_isa_factory_;
-  delete riscv_encoding_;
-  inst_db_->DecRef();
+  riscv_isa_factory_ = std::make_unique<RV32IsaFactory>();
+  riscv_isa_ = std::make_unique<isa32::RiscV32GInstructionSet>(
+      state, riscv_isa_factory_.get());
+  riscv_encoding_ = std::make_unique<isa32::RiscV32GEncoding>(state);
+  decoder_ = std::make_unique<
+      RiscVGenericDecoder<isa32::OpcodeEnum, isa32::RiscV32GEncoding,
+                          isa32::RiscV32GInstructionSet>>(
+      state, memory, riscv_encoding_.get(), riscv_isa_.get());
 }
 
 generic::Instruction* RiscV32Decoder::DecodeInstruction(uint64_t address) {
-  // First check that the address is aligned properly. If not, create and return
-  // an instruction object that will raise an exception.
-  if (address & 0x1) {
-    auto* inst = new generic::Instruction(0, state_);
-    inst->set_size(1);
-    inst->SetDisassemblyString("Misaligned instruction address");
-    inst->set_opcode(*isa32::OpcodeEnum::kNone);
-    inst->set_address(address);
-    inst->set_semantic_function([this](generic::Instruction* inst) {
-      state_->Trap(/*is_interrupt*/ false, inst->address(),
-                   *ExceptionCode::kInstructionAddressMisaligned,
-                   inst->address() ^ 0x1, inst);
-    });
-    return inst;
-  }
-
-  // If the address is greater than the max address, return an instruction
-  // that will raise an exception.
-  if (address > state_->max_physical_address()) {
-    auto* inst = new generic::Instruction(0, state_);
-    inst->set_size(0);
-    inst->SetDisassemblyString("Instruction access fault");
-    inst->set_opcode(*isa32::OpcodeEnum::kNone);
-    inst->set_address(address);
-    inst->set_semantic_function([this](generic::Instruction* inst) {
-      state_->Trap(/*is_interrupt*/ false, inst->address(),
-                   *ExceptionCode::kInstructionAccessFault, inst->address(),
-                   nullptr);
-    });
-    return inst;
-  }
-
-  // Read the instruction word from memory and parse it in the encoding parser.
-  memory_->Load(address, inst_db_, nullptr, nullptr);
-  uint32_t iword = inst_db_->Get<uint32_t>(0);
-  riscv_encoding_->ParseInstruction(iword);
-
-  // Call the isa decoder to obtain a new instruction object for the instruction
-  // word that was parsed above.
-  auto* instruction = riscv_isa_->Decode(address, riscv_encoding_);
-  return instruction;
+  return decoder_->DecodeInstruction(address);
 }
 
 }  // namespace riscv
diff --git a/riscv/riscv32_decoder.h b/riscv/riscv32_decoder.h
index e557ef8..4926a79 100644
--- a/riscv/riscv32_decoder.h
+++ b/riscv/riscv32_decoder.h
@@ -18,23 +18,19 @@
 #include <cstdint>
 #include <memory>
 
-#include "mpact/sim/generic/data_buffer.h"
 #include "mpact/sim/generic/decoder_interface.h"
 #include "mpact/sim/generic/instruction.h"
-#include "mpact/sim/generic/program_error.h"
-#include "mpact/sim/generic/type_helpers.h"
 #include "mpact/sim/util/memory/memory_interface.h"
 #include "riscv/riscv32g_decoder.h"
 #include "riscv/riscv32g_encoding.h"
 #include "riscv/riscv32g_enums.h"
+#include "riscv/riscv_generic_decoder.h"
 #include "riscv/riscv_state.h"
 
 namespace mpact {
 namespace sim {
 namespace riscv {
 
-using ::mpact::sim::generic::operator*;  // NOLINT: clang-tidy false positive.
-
 // This is the factory class needed by the generated decoder. It is responsible
 // for creating the decoder for each slot instance. Since the riscv architecture
 // only has a single slot, it's a pretty simple class.
@@ -55,7 +51,6 @@
 
   RiscV32Decoder(RiscVState* state, util::MemoryInterface* memory);
   RiscV32Decoder() = delete;
-  ~RiscV32Decoder() override;
 
   // This will always return a valid instruction that can be executed. In the
   // case of a decode error, the semantic function in the instruction object
@@ -69,16 +64,20 @@
   }
 
   // Getter.
-  isa32::RiscV32GEncoding* riscv_encoding() const { return riscv_encoding_; }
+  isa32::RiscV32GEncoding* riscv_encoding() const {
+    return riscv_encoding_.get();
+  }
 
  private:
   RiscVState* state_;
   util::MemoryInterface* memory_;
-  std::unique_ptr<generic::ProgramError> decode_error_;
-  generic::DataBuffer* inst_db_;
-  isa32::RiscV32GEncoding* riscv_encoding_;
-  RV32IsaFactory* riscv_isa_factory_;
-  isa32::RiscV32GInstructionSet* riscv_isa_;
+  std::unique_ptr<
+      RiscVGenericDecoder<isa32::OpcodeEnum, isa32::RiscV32GEncoding,
+                          isa32::RiscV32GInstructionSet>>
+      decoder_;
+  std::unique_ptr<isa32::RiscV32GEncoding> riscv_encoding_;
+  std::unique_ptr<RV32IsaFactory> riscv_isa_factory_;
+  std::unique_ptr<isa32::RiscV32GInstructionSet> riscv_isa_;
 };
 
 }  // namespace riscv
diff --git a/riscv/riscv32g_bitmanip_decoder.cc b/riscv/riscv32g_bitmanip_decoder.cc
index 3c2de3f..220a466 100644
--- a/riscv/riscv32g_bitmanip_decoder.cc
+++ b/riscv/riscv32g_bitmanip_decoder.cc
@@ -15,87 +15,38 @@
 #include "riscv/riscv32g_bitmanip_decoder.h"
 
 #include <cstdint>
-#include <new>
-#include <string>
+#include <memory>
 
 #include "mpact/sim/generic/instruction.h"
-#include "mpact/sim/generic/type_helpers.h"
 #include "mpact/sim/util/memory/memory_interface.h"
 #include "riscv/riscv32gzb_decoder.h"
 #include "riscv/riscv32gzb_encoding.h"
 #include "riscv/riscv32gzb_enums.h"
+#include "riscv/riscv_generic_decoder.h"
 #include "riscv/riscv_state.h"
 
 namespace mpact {
 namespace sim {
 namespace riscv {
 
-using ::mpact::sim::generic::operator*;  // NOLINT: is used below (clang error).
-
 RiscV32GBitmanipDecoder::RiscV32GBitmanipDecoder(RiscVState* state,
                                                  util::MemoryInterface* memory)
     : state_(state), memory_(memory) {
-  // Need a data buffer to load instructions from memory. Allocate a single
-  // buffer that can be reused for each instruction word.
-  inst_db_ = state_->db_factory()->Allocate<uint32_t>(1);
   // Allocate the isa factory class, the top level isa decoder instance, and
   // the encoding parser.
-  riscv_isa_factory_ = new RV32GZBIsaFactory();
-  riscv_isa_ =
-      new isa32gzb::RiscV32GZBInstructionSet(state, riscv_isa_factory_);
-  riscv_encoding_ = new isa32gzb::RiscV32GZBEncoding(state);
-}
-
-RiscV32GBitmanipDecoder::~RiscV32GBitmanipDecoder() {
-  delete riscv_isa_;
-  delete riscv_isa_factory_;
-  delete riscv_encoding_;
-  inst_db_->DecRef();
+  riscv_isa_factory_ = std::make_unique<RV32GZBIsaFactory>();
+  riscv_isa_ = std::make_unique<isa32gzb::RiscV32GZBInstructionSet>(
+      state, riscv_isa_factory_.get());
+  riscv_encoding_ = std::make_unique<isa32gzb::RiscV32GZBEncoding>(state);
+  decoder_ = std::make_unique<
+      RiscVGenericDecoder<isa32gzb::OpcodeEnum, isa32gzb::RiscV32GZBEncoding,
+                          isa32gzb::RiscV32GZBInstructionSet>>(
+      state, memory, riscv_encoding_.get(), riscv_isa_.get());
 }
 
 generic::Instruction* RiscV32GBitmanipDecoder::DecodeInstruction(
     uint64_t address) {
-  // First check that the address is aligned properly. If not, create and return
-  // an instruction object that will raise an exception.
-  if (address & 0x1) {
-    auto* inst = new generic::Instruction(0, state_);
-    inst->set_size(1);
-    inst->SetDisassemblyString("Misaligned instruction address");
-    inst->set_opcode(*isa32gzb::OpcodeEnum::kNone);
-    inst->set_address(address);
-    inst->set_semantic_function([this](generic::Instruction* inst) {
-      state_->Trap(/*is_interrupt*/ false, inst->address(),
-                   *ExceptionCode::kInstructionAddressMisaligned,
-                   inst->address() ^ 0x1, inst);
-    });
-    return inst;
-  }
-
-  // If the address is greater than the max address, return an instruction
-  // that will raise an exception.
-  if (address > state_->max_physical_address()) {
-    auto* inst = new generic::Instruction(0, state_);
-    inst->set_size(0);
-    inst->SetDisassemblyString("Instruction access fault");
-    inst->set_opcode(*isa32gzb::OpcodeEnum::kNone);
-    inst->set_address(address);
-    inst->set_semantic_function([this](generic::Instruction* inst) {
-      state_->Trap(/*is_interrupt*/ false, inst->address(),
-                   *ExceptionCode::kInstructionAccessFault, inst->address(),
-                   nullptr);
-    });
-    return inst;
-  }
-
-  // Read the instruction word from memory and parse it in the encoding parser.
-  memory_->Load(address, inst_db_, nullptr, nullptr);
-  uint32_t iword = inst_db_->Get<uint32_t>(0);
-  riscv_encoding_->ParseInstruction(iword);
-
-  // Call the isa decoder to obtain a new instruction object for the instruction
-  // word that was parsed above.
-  auto* instruction = riscv_isa_->Decode(address, riscv_encoding_);
-  return instruction;
+  return decoder_->DecodeInstruction(address);
 }
 
 }  // namespace riscv
diff --git a/riscv/riscv32g_bitmanip_decoder.h b/riscv/riscv32g_bitmanip_decoder.h
index 7c66ab1..8a54958 100644
--- a/riscv/riscv32g_bitmanip_decoder.h
+++ b/riscv/riscv32g_bitmanip_decoder.h
@@ -26,6 +26,7 @@
 #include "riscv/riscv32gzb_decoder.h"
 #include "riscv/riscv32gzb_encoding.h"
 #include "riscv/riscv32gzb_enums.h"
+#include "riscv/riscv_generic_decoder.h"
 #include "riscv/riscv_state.h"
 
 namespace mpact {
@@ -55,7 +56,6 @@
 
   RiscV32GBitmanipDecoder(RiscVState* state, util::MemoryInterface* memory);
   RiscV32GBitmanipDecoder() = delete;
-  ~RiscV32GBitmanipDecoder() override;
 
   // This will always return a valid instruction that can be executed. In the
   // case of a decode error, the semantic function in the instruction object
@@ -70,16 +70,20 @@
 
   // Getter.
   isa32gzb::RiscV32GZBEncoding* riscv_encoding() const {
-    return riscv_encoding_;
+    return riscv_encoding_.get();
   }
 
  private:
   RiscVState* state_;
+  std::unique_ptr<
+      RiscVGenericDecoder<isa32gzb::OpcodeEnum, isa32gzb::RiscV32GZBEncoding,
+                          isa32gzb::RiscV32GZBInstructionSet>>
+      decoder_;
   util::MemoryInterface* memory_;
   generic::DataBuffer* inst_db_;
-  isa32gzb::RiscV32GZBEncoding* riscv_encoding_;
-  RV32GZBIsaFactory* riscv_isa_factory_;
-  isa32gzb::RiscV32GZBInstructionSet* riscv_isa_;
+  std::unique_ptr<isa32gzb::RiscV32GZBEncoding> riscv_encoding_;
+  std::unique_ptr<RV32GZBIsaFactory> riscv_isa_factory_;
+  std::unique_ptr<isa32gzb::RiscV32GZBInstructionSet> riscv_isa_;
 };
 
 }  // namespace riscv
diff --git a/riscv/riscv32g_vec_decoder.cc b/riscv/riscv32g_vec_decoder.cc
index 49b0da8..f9f2939 100644
--- a/riscv/riscv32g_vec_decoder.cc
+++ b/riscv/riscv32g_vec_decoder.cc
@@ -14,10 +14,16 @@
 
 #include "riscv/riscv32g_vec_decoder.h"
 
-#include <string>
+#include <cstdint>
+#include <memory>
 
-#include "absl/strings/str_cat.h"
-#include "mpact/sim/generic/program_error.h"
+#include "mpact/sim/generic/instruction.h"
+#include "mpact/sim/util/memory/memory_interface.h"
+#include "riscv/riscv32g_vec_encoding.h"
+#include "riscv/riscv32gv_decoder.h"
+#include "riscv/riscv32gv_enums.h"
+#include "riscv/riscv_generic_decoder.h"
+#include "riscv/riscv_state.h"
 
 namespace mpact {
 namespace sim {
@@ -26,53 +32,20 @@
 RiscV32GVecDecoder::RiscV32GVecDecoder(RiscVState* state,
                                        util::MemoryInterface* memory)
     : state_(state), memory_(memory) {
-  // Get a handle to the internal error in the program error controller.
-  decode_error_ = state->program_error_controller()->GetProgramError(
-      generic::ProgramErrorController::kInternalErrorName);
-
-  // Need a data buffer to load instructions from memory. Allocate a single
-  // buffer that can be reused for each instruction word.
-  inst_db_ = state_->db_factory()->Allocate<uint32_t>(1);
   // Allocate the isa factory class, the top level isa decoder instance, and
   // the encoding parser.
-  riscv_isa_factory_ = new RV32GVIsaFactory();
-  riscv_isa_ = new isa32v::RiscV32GVInstructionSet(state, riscv_isa_factory_);
-  riscv_encoding_ = new isa32v::RiscV32GVecEncoding(state);
-  decode_error_ = state->program_error_controller()->GetProgramError(
-      generic::ProgramErrorController::kInternalErrorName);
-}
-
-RiscV32GVecDecoder::~RiscV32GVecDecoder() {
-  inst_db_->DecRef();
-  delete riscv_isa_;
-  delete riscv_isa_factory_;
-  delete riscv_encoding_;
+  riscv_isa_factory_ = std::make_unique<RV32GVIsaFactory>();
+  riscv_isa_ = std::make_unique<isa32v::RiscV32GVInstructionSet>(
+      state, riscv_isa_factory_.get());
+  riscv_encoding_ = std::make_unique<isa32v::RiscV32GVecEncoding>(state);
+  decoder_ = std::make_unique<
+      RiscVGenericDecoder<isa32v::OpcodeEnum, isa32v::RiscV32GVecEncoding,
+                          isa32v::RiscV32GVInstructionSet>>(
+      state, memory, riscv_encoding_.get(), riscv_isa_.get());
 }
 
 generic::Instruction* RiscV32GVecDecoder::DecodeInstruction(uint64_t address) {
-  // First check that the address is aligned properly. If not, create and return
-  // an instruction object that generates an internal simulator error when
-  // executed.
-  if (address & 0x1) {
-    auto* inst = new generic::Instruction(address, state_);
-    std::string error =
-        absl::StrCat(absl::Hex(address), ": unaligned instruction address");
-    inst->set_semantic_function([error, this](generic::Instruction* inst) {
-      decode_error_->Raise(error);
-    });
-    inst->set_size(1);
-    return inst;
-  }
-
-  // Read the instruction word from memory and parse it in the encoding parser.
-  memory_->Load(address, inst_db_, nullptr, nullptr);
-  uint32_t iword = inst_db_->Get<uint32_t>(0);
-  riscv_encoding_->ParseInstruction(iword);
-
-  // Call the isa decoder to obtain a new instruction object for the instruction
-  // word that was parsed above.
-  auto* instruction = riscv_isa_->Decode(address, riscv_encoding_);
-  return instruction;
+  return decoder_->DecodeInstruction(address);
 }
 
 }  // namespace riscv
diff --git a/riscv/riscv32g_vec_decoder.h b/riscv/riscv32g_vec_decoder.h
index f75ede6..a6c38e9 100644
--- a/riscv/riscv32g_vec_decoder.h
+++ b/riscv/riscv32g_vec_decoder.h
@@ -18,15 +18,14 @@
 #include <cstdint>
 #include <memory>
 
-#include "mpact/sim/generic/data_buffer.h"
 #include "mpact/sim/generic/decoder_interface.h"
 #include "mpact/sim/generic/instruction.h"
-#include "mpact/sim/generic/program_error.h"
 #include "mpact/sim/generic/type_helpers.h"
 #include "mpact/sim/util/memory/memory_interface.h"
 #include "riscv/riscv32g_vec_encoding.h"
 #include "riscv/riscv32gv_decoder.h"
 #include "riscv/riscv32gv_enums.h"
+#include "riscv/riscv_generic_decoder.h"
 #include "riscv/riscv_state.h"
 
 namespace mpact {
@@ -55,7 +54,6 @@
 
   RiscV32GVecDecoder(RiscVState* state, util::MemoryInterface* memory);
   RiscV32GVecDecoder() = delete;
-  ~RiscV32GVecDecoder() override;
 
   // This will always return a valid instruction that can be executed. In the
   // case of a decode error, the semantic function in the instruction object
@@ -71,17 +69,19 @@
 
   // Getter.
   isa32v::RiscV32GVecEncoding* riscv_encoding() const {
-    return riscv_encoding_;
+    return riscv_encoding_.get();
   }
 
  private:
   RiscVState* state_;
   util::MemoryInterface* memory_;
-  std::unique_ptr<generic::ProgramError> decode_error_;
-  generic::DataBuffer* inst_db_;
-  isa32v::RiscV32GVecEncoding* riscv_encoding_;
-  RV32GVIsaFactory* riscv_isa_factory_;
-  isa32v::RiscV32GVInstructionSet* riscv_isa_;
+  std::unique_ptr<
+      RiscVGenericDecoder<isa32v::OpcodeEnum, isa32v::RiscV32GVecEncoding,
+                          isa32v::RiscV32GVInstructionSet>>
+      decoder_;
+  std::unique_ptr<isa32v::RiscV32GVecEncoding> riscv_encoding_;
+  std::unique_ptr<RV32GVIsaFactory> riscv_isa_factory_;
+  std::unique_ptr<isa32v::RiscV32GVInstructionSet> riscv_isa_;
 };
 
 }  // namespace riscv
diff --git a/riscv/riscv32gzb_vec_decoder.cc b/riscv/riscv32gzb_vec_decoder.cc
index 3318637..c4e2498 100644
--- a/riscv/riscv32gzb_vec_decoder.cc
+++ b/riscv/riscv32gzb_vec_decoder.cc
@@ -16,16 +16,14 @@
 
 #include <cstdint>
 #include <memory>
-#include <new>
-#include <string>
 
 #include "mpact/sim/generic/instruction.h"
-#include "mpact/sim/generic/program_error.h"
 #include "mpact/sim/generic/type_helpers.h"
 #include "mpact/sim/util/memory/memory_interface.h"
 #include "riscv/riscv32gvzb_decoder.h"
 #include "riscv/riscv32gvzb_enums.h"
 #include "riscv/riscv32gzb_vec_encoding.h"
+#include "riscv/riscv_generic_decoder.h"
 #include "riscv/riscv_state.h"
 
 namespace mpact {
@@ -36,67 +34,22 @@
 
 RiscV32GZBVecDecoder::RiscV32GZBVecDecoder(RiscVState* state,
                                            util::MemoryInterface* memory)
-    : state_(state),
-      memory_(memory),
-      inst_db_(state_->db_factory()->Allocate<uint32_t>(1)) {
-  // Get a handle to the internal error in the program error controller.
-  decode_error_ = state->program_error_controller()->GetProgramError(
-      generic::ProgramErrorController::kInternalErrorName);
-
+    : state_(state), memory_(memory) {
   // Allocate the isa factory class, the top level isa decoder instance, and
   // the encoding parser.
   riscv_isa_factory_ = std::make_unique<RV32GVZBIsaFactory>();
   riscv_isa_ = std::make_unique<isa32gvzb::RiscV32GVZBInstructionSet>(
       state, riscv_isa_factory_.get());
   riscv_encoding_ = std::make_unique<isa32gvzb::RiscV32GZBVecEncoding>(state);
-  decode_error_ = state->program_error_controller()->GetProgramError(
-      generic::ProgramErrorController::kInternalErrorName);
+  decoder_ = std::make_unique<RiscVGenericDecoder<
+      isa32gvzb::OpcodeEnum, isa32gvzb::RiscV32GZBVecEncoding,
+      isa32gvzb::RiscV32GVZBInstructionSet>>(
+      state, memory, riscv_encoding_.get(), riscv_isa_.get());
 }
 
-RiscV32GZBVecDecoder::~RiscV32GZBVecDecoder() { inst_db_->DecRef(); }
-
 generic::Instruction* RiscV32GZBVecDecoder::DecodeInstruction(
     uint64_t address) {
-  // First check that the address is aligned properly. If not, create and return
-  // an instruction object that will raise an exception.
-  if (address & 0x1) {
-    auto* inst = new generic::Instruction(address, state_);
-    inst->set_size(1);
-    inst->SetDisassemblyString("Misaligned instruction address");
-    inst->set_opcode(*isa32gvzb::OpcodeEnum::kNone);
-    inst->set_address(address);
-    inst->set_semantic_function([this, address](generic::Instruction* inst) {
-      state_->Trap(/*is_interrupt*/ false, address,
-                   *ExceptionCode::kInstructionAddressMisaligned, address ^ 0x1,
-                   inst);
-    });
-    return inst;
-  }
-
-  // If the address is greater than the max address, return an instruction
-  // object that will raise an exception.
-  if (address > state_->max_physical_address()) {
-    auto* inst = new generic::Instruction(address, state_);
-    inst->set_size(0);
-    inst->SetDisassemblyString("Instruction access fault");
-    inst->set_opcode(*isa32gvzb::OpcodeEnum::kNone);
-    inst->set_address(address);
-    inst->set_semantic_function([this, address](generic::Instruction* inst) {
-      state_->Trap(/*is_interrupt*/ false, address,
-                   *ExceptionCode::kInstructionAccessFault, address, nullptr);
-    });
-    return inst;
-  }
-
-  // Read the instruction word from memory and parse it in the encoding parser.
-  memory_->Load(address, inst_db_, nullptr, nullptr);
-  const uint32_t iword = inst_db_->Get<uint32_t>(0);
-  riscv_encoding_->ParseInstruction(iword);
-
-  // Call the isa decoder to obtain a new instruction object for the instruction
-  // word that was parsed above.
-  auto* instruction = riscv_isa_->Decode(address, riscv_encoding_.get());
-  return instruction;
+  return decoder_->DecodeInstruction(address);
 }
 
 }  // namespace riscv
diff --git a/riscv/riscv32gzb_vec_decoder.h b/riscv/riscv32gzb_vec_decoder.h
index b5cc9e9..8039cea 100644
--- a/riscv/riscv32gzb_vec_decoder.h
+++ b/riscv/riscv32gzb_vec_decoder.h
@@ -18,15 +18,14 @@
 #include <cstdint>
 #include <memory>
 
-#include "mpact/sim/generic/data_buffer.h"
 #include "mpact/sim/generic/decoder_interface.h"
 #include "mpact/sim/generic/instruction.h"
-#include "mpact/sim/generic/program_error.h"
 #include "mpact/sim/generic/type_helpers.h"
 #include "mpact/sim/util/memory/memory_interface.h"
 #include "riscv/riscv32gvzb_decoder.h"
 #include "riscv/riscv32gvzb_enums.h"
 #include "riscv/riscv32gzb_vec_encoding.h"
+#include "riscv/riscv_generic_decoder.h"
 #include "riscv/riscv_state.h"
 
 namespace mpact {
@@ -56,7 +55,6 @@
 
   RiscV32GZBVecDecoder(RiscVState* state, util::MemoryInterface* memory);
   RiscV32GZBVecDecoder() = delete;
-  ~RiscV32GZBVecDecoder() override;
 
   // This will always return a valid instruction that can be executed. In the
   // case of a decode error, the semantic function in the instruction object
@@ -72,12 +70,10 @@
  private:
   RiscVState* const state_;
   util::MemoryInterface* const memory_;
-
-  // Buffer used to load instructions from memory. Re-used for each instruction
-  // word.
-  generic::DataBuffer* const inst_db_;
-
-  std::unique_ptr<generic::ProgramError> decode_error_;
+  std::unique_ptr<RiscVGenericDecoder<isa32gvzb::OpcodeEnum,
+                                      isa32gvzb::RiscV32GZBVecEncoding,
+                                      isa32gvzb::RiscV32GVZBInstructionSet>>
+      decoder_;
   std::unique_ptr<isa32gvzb::RiscV32GZBVecEncoding> riscv_encoding_;
   std::unique_ptr<RV32GVZBIsaFactory> riscv_isa_factory_;
   std::unique_ptr<isa32gvzb::RiscV32GVZBInstructionSet> riscv_isa_;
diff --git a/riscv/riscv64_decoder.cc b/riscv/riscv64_decoder.cc
index 39da434..3bb579c 100644
--- a/riscv/riscv64_decoder.cc
+++ b/riscv/riscv64_decoder.cc
@@ -15,94 +15,41 @@
 #include "riscv/riscv64_decoder.h"
 
 #include <cstdint>
-#include <string>
+#include <memory>
 
-#include "absl/strings/str_cat.h"
 #include "mpact/sim/generic/instruction.h"
-#include "mpact/sim/generic/program_error.h"
-#include "mpact/sim/generic/type_helpers.h"
 #include "mpact/sim/util/memory/memory_interface.h"
 #include "riscv/riscv64g_decoder.h"
 #include "riscv/riscv64g_encoding.h"
 #include "riscv/riscv64g_enums.h"
+#include "riscv/riscv_generic_decoder.h"
 #include "riscv/riscv_state.h"
 
 namespace mpact {
 namespace sim {
 namespace riscv {
 
-using ::mpact::sim::generic::operator*;  // NOLINT: is used below (clang error).
-
 RiscV64Decoder::RiscV64Decoder(RiscVState* state, util::MemoryInterface* memory)
     : RiscV64Decoder(state, memory, /*use_abi_names*/ true) {}
 
 RiscV64Decoder::RiscV64Decoder(RiscVState* state, util::MemoryInterface* memory,
                                bool use_abi_names)
     : state_(state), memory_(memory) {
-  // Get a handle to the internal error in the program error controller.
-  decode_error_ = state->program_error_controller()->GetProgramError(
-      generic::ProgramErrorController::kInternalErrorName);
-
-  // Need a data buffer to load instructions from memory. Allocate a single
-  // buffer that can be reused for each instruction word.
-  inst_db_ = state_->db_factory()->Allocate<uint32_t>(1);
   // Allocate the isa factory class, the top level isa decoder instance, and
   // the encoding parser.
-  riscv_isa_factory_ = new RV64IsaFactory();
-  riscv_isa_ = new isa64::RiscV64GInstructionSet(state, riscv_isa_factory_);
-  riscv_encoding_ = new isa64::RiscV64GEncoding(state, use_abi_names);
-  decode_error_ = state->program_error_controller()->GetProgramError(
-      generic::ProgramErrorController::kInternalErrorName);
-}
-
-RiscV64Decoder::~RiscV64Decoder() {
-  inst_db_->DecRef();
-  delete riscv_isa_;
-  delete riscv_isa_factory_;
-  delete riscv_encoding_;
+  riscv_isa_factory_ = std::make_unique<RV64IsaFactory>();
+  riscv_isa_ = std::make_unique<isa64::RiscV64GInstructionSet>(
+      state, riscv_isa_factory_.get());
+  riscv_encoding_ =
+      std::make_unique<isa64::RiscV64GEncoding>(state, use_abi_names);
+  decoder_ = std::make_unique<
+      RiscVGenericDecoder<isa64::OpcodeEnum, isa64::RiscV64GEncoding,
+                          isa64::RiscV64GInstructionSet>>(
+      state, memory, riscv_encoding_.get(), riscv_isa_.get());
 }
 
 generic::Instruction* RiscV64Decoder::DecodeInstruction(uint64_t address) {
-  // First check that the address is aligned properly. If not, create and return
-  // an instruction object that will raise an exception.
-  if (address & 0x1) {
-    auto* inst = new generic::Instruction(address, state_);
-    inst->set_size(1);
-    inst->SetDisassemblyString("Misaligned instruction address");
-    inst->set_opcode(*isa64::OpcodeEnum::kNone);
-    inst->set_address(address);
-    inst->set_semantic_function([this, address](generic::Instruction* inst) {
-      state_->Trap(/*is_interrupt*/ false, address,
-                   *ExceptionCode::kInstructionAddressMisaligned, address ^ 0x1,
-                   inst);
-    });
-    return inst;
-  }
-
-  // If the address is greater than the max address, return an instruction
-  // object that will raise an exception.
-  if (address > state_->max_physical_address()) {
-    auto* inst = new generic::Instruction(address, state_);
-    inst->set_size(0);
-    inst->SetDisassemblyString("Instruction access fault");
-    inst->set_opcode(*isa64::OpcodeEnum::kNone);
-    inst->set_address(address);
-    inst->set_semantic_function([this, address](generic::Instruction* inst) {
-      state_->Trap(/*is_interrupt*/ false, address,
-                   *ExceptionCode::kInstructionAccessFault, address, nullptr);
-    });
-    return inst;
-  }
-
-  // Read the instruction word from memory and parse it in the encoding parser.
-  memory_->Load(address, inst_db_, nullptr, nullptr);
-  uint32_t iword = inst_db_->Get<uint32_t>(0);
-  riscv_encoding_->ParseInstruction(iword);
-
-  // Call the isa decoder to obtain a new instruction object for the instruction
-  // word that was parsed above.
-  auto* instruction = riscv_isa_->Decode(address, riscv_encoding_);
-  return instruction;
+  return decoder_->DecodeInstruction(address);
 }
 
 }  // namespace riscv
diff --git a/riscv/riscv64_decoder.h b/riscv/riscv64_decoder.h
index 6d5422f..a109899 100644
--- a/riscv/riscv64_decoder.h
+++ b/riscv/riscv64_decoder.h
@@ -18,15 +18,14 @@
 #include <cstdint>
 #include <memory>
 
-#include "mpact/sim/generic/data_buffer.h"
 #include "mpact/sim/generic/decoder_interface.h"
 #include "mpact/sim/generic/instruction.h"
-#include "mpact/sim/generic/program_error.h"
 #include "mpact/sim/generic/type_helpers.h"
 #include "mpact/sim/util/memory/memory_interface.h"
 #include "riscv/riscv64g_decoder.h"
 #include "riscv/riscv64g_encoding.h"
 #include "riscv/riscv64g_enums.h"
+#include "riscv/riscv_generic_decoder.h"
 #include "riscv/riscv_state.h"
 
 namespace mpact {
@@ -57,7 +56,6 @@
   RiscV64Decoder(RiscVState* state, util::MemoryInterface* memory,
                  bool use_abi_names);
   RiscV64Decoder() = delete;
-  ~RiscV64Decoder() override;
 
   // This will always return a valid instruction that can be executed. In the
   // case of a decode error, the semantic function in the instruction object
@@ -71,16 +69,20 @@
   }
 
   // Getter.
-  isa64::RiscV64GEncoding* riscv_encoding() const { return riscv_encoding_; }
+  isa64::RiscV64GEncoding* riscv_encoding() const {
+    return riscv_encoding_.get();
+  }
 
  private:
   RiscVState* state_;
   util::MemoryInterface* memory_;
-  std::unique_ptr<generic::ProgramError> decode_error_;
-  generic::DataBuffer* inst_db_;
-  isa64::RiscV64GEncoding* riscv_encoding_;
-  RV64IsaFactory* riscv_isa_factory_;
-  isa64::RiscV64GInstructionSet* riscv_isa_;
+  std::unique_ptr<
+      RiscVGenericDecoder<isa64::OpcodeEnum, isa64::RiscV64GEncoding,
+                          isa64::RiscV64GInstructionSet>>
+      decoder_;
+  std::unique_ptr<isa64::RiscV64GEncoding> riscv_encoding_;
+  std::unique_ptr<RV64IsaFactory> riscv_isa_factory_;
+  std::unique_ptr<isa64::RiscV64GInstructionSet> riscv_isa_;
 };
 
 }  // namespace riscv
diff --git a/riscv/riscv64g_bitmanip_decoder.cc b/riscv/riscv64g_bitmanip_decoder.cc
index a69b537..c6adf3f 100644
--- a/riscv/riscv64g_bitmanip_decoder.cc
+++ b/riscv/riscv64g_bitmanip_decoder.cc
@@ -15,87 +15,38 @@
 #include "riscv/riscv64g_bitmanip_decoder.h"
 
 #include <cstdint>
-#include <new>
-#include <string>
+#include <memory>
 
 #include "mpact/sim/generic/instruction.h"
-#include "mpact/sim/generic/type_helpers.h"
 #include "mpact/sim/util/memory/memory_interface.h"
 #include "riscv/riscv64gzb_decoder.h"
 #include "riscv/riscv64gzb_encoding.h"
 #include "riscv/riscv64gzb_enums.h"
+#include "riscv/riscv_generic_decoder.h"
 #include "riscv/riscv_state.h"
 
 namespace mpact {
 namespace sim {
 namespace riscv {
 
-using ::mpact::sim::generic::operator*;  // NOLINT: is used below (clang error).
-
 RiscV64GBitmanipDecoder::RiscV64GBitmanipDecoder(RiscVState* state,
                                                  util::MemoryInterface* memory)
     : state_(state), memory_(memory) {
-  // Need a data buffer to load instructions from memory. Allocate a single
-  // buffer that can be reused for each instruction word.
-  inst_db_ = state_->db_factory()->Allocate<uint32_t>(1);
   // Allocate the isa factory class, the top level isa decoder instance, and
   // the encoding parser.
-  riscv_isa_factory_ = new RV64GZBIsaFactory();
-  riscv_isa_ =
-      new isa64gzb::RiscV64GZBInstructionSet(state, riscv_isa_factory_);
-  riscv_encoding_ = new isa64gzb::RiscV64GZBEncoding(state);
-}
-
-RiscV64GBitmanipDecoder::~RiscV64GBitmanipDecoder() {
-  delete riscv_isa_;
-  delete riscv_isa_factory_;
-  delete riscv_encoding_;
-  inst_db_->DecRef();
+  riscv_isa_factory_ = std::make_unique<RV64GZBIsaFactory>();
+  riscv_isa_ = std::make_unique<isa64gzb::RiscV64GZBInstructionSet>(
+      state, riscv_isa_factory_.get());
+  riscv_encoding_ = std::make_unique<isa64gzb::RiscV64GZBEncoding>(state);
+  decoder_ = std::make_unique<
+      RiscVGenericDecoder<isa64gzb::OpcodeEnum, isa64gzb::RiscV64GZBEncoding,
+                          isa64gzb::RiscV64GZBInstructionSet>>(
+      state, memory, riscv_encoding_.get(), riscv_isa_.get());
 }
 
 generic::Instruction* RiscV64GBitmanipDecoder::DecodeInstruction(
     uint64_t address) {
-  // First check that the address is aligned properly. If not, create and return
-  // an instruction object that will raise an exception.
-  if (address & 0x1) {
-    auto* inst = new generic::Instruction(0, state_);
-    inst->set_size(1);
-    inst->SetDisassemblyString("Misaligned instruction address");
-    inst->set_opcode(*isa64gzb::OpcodeEnum::kNone);
-    inst->set_address(address);
-    inst->set_semantic_function([this](generic::Instruction* inst) {
-      state_->Trap(/*is_interrupt*/ false, inst->address(),
-                   *ExceptionCode::kInstructionAddressMisaligned,
-                   inst->address() ^ 0x1, inst);
-    });
-    return inst;
-  }
-
-  // If the address is greater than the max address, return an instruction
-  // that will raise an exception.
-  if (address > state_->max_physical_address()) {
-    auto* inst = new generic::Instruction(0, state_);
-    inst->set_size(0);
-    inst->SetDisassemblyString("Instruction access fault");
-    inst->set_opcode(*isa64gzb::OpcodeEnum::kNone);
-    inst->set_address(address);
-    inst->set_semantic_function([this](generic::Instruction* inst) {
-      state_->Trap(/*is_interrupt*/ false, inst->address(),
-                   *ExceptionCode::kInstructionAccessFault, inst->address(),
-                   nullptr);
-    });
-    return inst;
-  }
-
-  // Read the instruction word from memory and parse it in the encoding parser.
-  memory_->Load(address, inst_db_, nullptr, nullptr);
-  uint32_t iword = inst_db_->Get<uint32_t>(0);
-  riscv_encoding_->ParseInstruction(iword);
-
-  // Call the isa decoder to obtain a new instruction object for the instruction
-  // word that was parsed above.
-  auto* instruction = riscv_isa_->Decode(address, riscv_encoding_);
-  return instruction;
+  return decoder_->DecodeInstruction(address);
 }
 
 }  // namespace riscv
diff --git a/riscv/riscv64g_bitmanip_decoder.h b/riscv/riscv64g_bitmanip_decoder.h
index 683dbde..847243a 100644
--- a/riscv/riscv64g_bitmanip_decoder.h
+++ b/riscv/riscv64g_bitmanip_decoder.h
@@ -18,14 +18,13 @@
 #include <cstdint>
 #include <memory>
 
-#include "mpact/sim/generic/data_buffer.h"
 #include "mpact/sim/generic/decoder_interface.h"
 #include "mpact/sim/generic/instruction.h"
-#include "mpact/sim/generic/type_helpers.h"
 #include "mpact/sim/util/memory/memory_interface.h"
 #include "riscv/riscv64gzb_decoder.h"
 #include "riscv/riscv64gzb_encoding.h"
 #include "riscv/riscv64gzb_enums.h"
+#include "riscv/riscv_generic_decoder.h"
 #include "riscv/riscv_state.h"
 
 namespace mpact {
@@ -33,7 +32,6 @@
 namespace riscv {
 
 using ::mpact::sim::generic::Instruction;
-using ::mpact::sim::generic::operator*;  // NOLINT: clang-tidy false positive.
 
 // This is the factory class needed by the generated decoder. It is responsible
 // for creating the decoder for each slot instance. Since the riscv architecture
@@ -55,7 +53,6 @@
 
   RiscV64GBitmanipDecoder(RiscVState* state, util::MemoryInterface* memory);
   RiscV64GBitmanipDecoder() = delete;
-  ~RiscV64GBitmanipDecoder() override;
 
   // This will always return a valid instruction that can be executed. In the
   // case of a decode error, the semantic function in the instruction object
@@ -70,16 +67,19 @@
 
   // Getter.
   isa64gzb::RiscV64GZBEncoding* riscv_encoding() const {
-    return riscv_encoding_;
+    return riscv_encoding_.get();
   }
 
  private:
   RiscVState* state_;
   util::MemoryInterface* memory_;
-  generic::DataBuffer* inst_db_;
-  isa64gzb::RiscV64GZBEncoding* riscv_encoding_;
-  RV64GZBIsaFactory* riscv_isa_factory_;
-  isa64gzb::RiscV64GZBInstructionSet* riscv_isa_;
+  std::unique_ptr<
+      RiscVGenericDecoder<isa64gzb::OpcodeEnum, isa64gzb::RiscV64GZBEncoding,
+                          isa64gzb::RiscV64GZBInstructionSet>>
+      decoder_;
+  std::unique_ptr<isa64gzb::RiscV64GZBEncoding> riscv_encoding_;
+  std::unique_ptr<RV64GZBIsaFactory> riscv_isa_factory_;
+  std::unique_ptr<isa64gzb::RiscV64GZBInstructionSet> riscv_isa_;
 };
 
 }  // namespace riscv
diff --git a/riscv/riscv64g_vec_decoder.cc b/riscv/riscv64g_vec_decoder.cc
index f3fde6e..9707e09 100644
--- a/riscv/riscv64g_vec_decoder.cc
+++ b/riscv/riscv64g_vec_decoder.cc
@@ -16,86 +16,36 @@
 
 #include <cstdint>
 #include <memory>
-#include <new>
-#include <string>
 
 #include "mpact/sim/generic/instruction.h"
-#include "mpact/sim/generic/program_error.h"
-#include "mpact/sim/generic/type_helpers.h"
 #include "mpact/sim/util/memory/memory_interface.h"
 #include "riscv/riscv64g_vec_encoding.h"
 #include "riscv/riscv64gv_decoder.h"
 #include "riscv/riscv64gv_enums.h"
+#include "riscv/riscv_generic_decoder.h"
 #include "riscv/riscv_state.h"
 
 namespace mpact {
 namespace sim {
 namespace riscv {
 
-using ::mpact::sim::generic::operator*;  // NOLINT: is used below (clang error).
-
 RiscV64GVecDecoder::RiscV64GVecDecoder(RiscVState* state,
                                        util::MemoryInterface* memory)
-    : state_(state),
-      memory_(memory),
-      inst_db_(state_->db_factory()->Allocate<uint32_t>(1)) {
-  // Get a handle to the internal error in the program error controller.
-  decode_error_ = state->program_error_controller()->GetProgramError(
-      generic::ProgramErrorController::kInternalErrorName);
-
+    : state_(state), memory_(memory) {
   // Allocate the isa factory class, the top level isa decoder instance, and
   // the encoding parser.
   riscv_isa_factory_ = std::make_unique<RV64GVIsaFactory>();
   riscv_isa_ = std::make_unique<isa64v::RiscV64GVInstructionSet>(
       state, riscv_isa_factory_.get());
   riscv_encoding_ = std::make_unique<isa64v::RiscV64GVecEncoding>(state);
-  decode_error_ = state->program_error_controller()->GetProgramError(
-      generic::ProgramErrorController::kInternalErrorName);
+  decoder_ = std::make_unique<
+      RiscVGenericDecoder<isa64v::OpcodeEnum, isa64v::RiscV64GVecEncoding,
+                          isa64v::RiscV64GVInstructionSet>>(
+      state, memory, riscv_encoding_.get(), riscv_isa_.get());
 }
 
-RiscV64GVecDecoder::~RiscV64GVecDecoder() { inst_db_->DecRef(); }
-
 generic::Instruction* RiscV64GVecDecoder::DecodeInstruction(uint64_t address) {
-  // First check that the address is aligned properly. If not, create and return
-  // an instruction object that will raise an exception.
-  if (address & 0x1) {
-    auto* inst = new generic::Instruction(address, state_);
-    inst->set_size(1);
-    inst->SetDisassemblyString("Misaligned instruction address");
-    inst->set_opcode(*isa64v::OpcodeEnum::kNone);
-    inst->set_address(address);
-    inst->set_semantic_function([this, address](generic::Instruction* inst) {
-      state_->Trap(/*is_interrupt*/ false, address,
-                   *ExceptionCode::kInstructionAddressMisaligned, address ^ 0x1,
-                   inst);
-    });
-    return inst;
-  }
-
-  // If the address is greater than the max address, return an instruction
-  // object that will raise an exception.
-  if (address > state_->max_physical_address()) {
-    auto* inst = new generic::Instruction(address, state_);
-    inst->set_size(0);
-    inst->SetDisassemblyString("Instruction access fault");
-    inst->set_opcode(*isa64v::OpcodeEnum::kNone);
-    inst->set_address(address);
-    inst->set_semantic_function([this, address](generic::Instruction* inst) {
-      state_->Trap(/*is_interrupt*/ false, address,
-                   *ExceptionCode::kInstructionAccessFault, address, nullptr);
-    });
-    return inst;
-  }
-
-  // Read the instruction word from memory and parse it in the encoding parser.
-  memory_->Load(address, inst_db_, nullptr, nullptr);
-  const uint32_t iword = inst_db_->Get<uint32_t>(0);
-  riscv_encoding_->ParseInstruction(iword);
-
-  // Call the isa decoder to obtain a new instruction object for the instruction
-  // word that was parsed above.
-  auto* instruction = riscv_isa_->Decode(address, riscv_encoding_.get());
-  return instruction;
+  return decoder_->DecodeInstruction(address);
 }
 
 }  // namespace riscv
diff --git a/riscv/riscv64g_vec_decoder.h b/riscv/riscv64g_vec_decoder.h
index 9a651cf..0b584f1 100644
--- a/riscv/riscv64g_vec_decoder.h
+++ b/riscv/riscv64g_vec_decoder.h
@@ -18,23 +18,19 @@
 #include <cstdint>
 #include <memory>
 
-#include "mpact/sim/generic/data_buffer.h"
 #include "mpact/sim/generic/decoder_interface.h"
 #include "mpact/sim/generic/instruction.h"
-#include "mpact/sim/generic/program_error.h"
-#include "mpact/sim/generic/type_helpers.h"
 #include "mpact/sim/util/memory/memory_interface.h"
 #include "riscv/riscv64g_vec_encoding.h"
 #include "riscv/riscv64gv_decoder.h"
 #include "riscv/riscv64gv_enums.h"
+#include "riscv/riscv_generic_decoder.h"
 #include "riscv/riscv_state.h"
 
 namespace mpact {
 namespace sim {
 namespace riscv {
 
-using ::mpact::sim::generic::operator*;  // NOLINT: clang-tidy false positive.
-
 // This is the factory class needed by the generated decoder. It is responsible
 // for creating the decoder for each slot instance. Since the riscv architecture
 // only has a single slot, it's a pretty simple class.
@@ -56,7 +52,6 @@
 
   RiscV64GVecDecoder(RiscVState* state, util::MemoryInterface* memory);
   RiscV64GVecDecoder() = delete;
-  ~RiscV64GVecDecoder() override;
 
   // This will always return a valid instruction that can be executed. In the
   // case of a decode error, the semantic function in the instruction object
@@ -69,15 +64,18 @@
     return isa64v::kOpcodeNames[index];
   }
 
+  // Getter.
+  isa64v::RiscV64GVecEncoding* riscv_encoding() const {
+    return riscv_encoding_.get();
+  }
+
  private:
   RiscVState* const state_;
   util::MemoryInterface* const memory_;
-
-  // Buffer used to load instructions from memory. Re-used for each instruction
-  // word.
-  generic::DataBuffer* const inst_db_;
-
-  std::unique_ptr<generic::ProgramError> decode_error_;
+  std::unique_ptr<
+      RiscVGenericDecoder<isa64v::OpcodeEnum, isa64v::RiscV64GVecEncoding,
+                          isa64v::RiscV64GVInstructionSet>>
+      decoder_;
   std::unique_ptr<isa64v::RiscV64GVecEncoding> riscv_encoding_;
   std::unique_ptr<RV64GVIsaFactory> riscv_isa_factory_;
   std::unique_ptr<isa64v::RiscV64GVInstructionSet> riscv_isa_;
diff --git a/riscv/riscv64gzb_vec_decoder.cc b/riscv/riscv64gzb_vec_decoder.cc
index a578f89..bc5bbbe 100644
--- a/riscv/riscv64gzb_vec_decoder.cc
+++ b/riscv/riscv64gzb_vec_decoder.cc
@@ -16,87 +16,37 @@
 
 #include <cstdint>
 #include <memory>
-#include <new>
-#include <string>
 
 #include "mpact/sim/generic/instruction.h"
-#include "mpact/sim/generic/program_error.h"
-#include "mpact/sim/generic/type_helpers.h"
 #include "mpact/sim/util/memory/memory_interface.h"
 #include "riscv/riscv64gvzb_decoder.h"
 #include "riscv/riscv64gvzb_enums.h"
 #include "riscv/riscv64gzb_vec_encoding.h"
+#include "riscv/riscv_generic_decoder.h"
 #include "riscv/riscv_state.h"
 
 namespace mpact {
 namespace sim {
 namespace riscv {
 
-using ::mpact::sim::generic::operator*;  // NOLINT: is used below (clang error).
-
 RiscV64GZBVecDecoder::RiscV64GZBVecDecoder(RiscVState* state,
                                            util::MemoryInterface* memory)
-    : state_(state),
-      memory_(memory),
-      inst_db_(state_->db_factory()->Allocate<uint32_t>(1)) {
-  // Get a handle to the internal error in the program error controller.
-  decode_error_ = state->program_error_controller()->GetProgramError(
-      generic::ProgramErrorController::kInternalErrorName);
-
+    : state_(state), memory_(memory) {
   // Allocate the isa factory class, the top level isa decoder instance, and
   // the encoding parser.
   riscv_isa_factory_ = std::make_unique<RV64GVZBIsaFactory>();
   riscv_isa_ = std::make_unique<isa64gvzb::RiscV64GVZBInstructionSet>(
       state, riscv_isa_factory_.get());
   riscv_encoding_ = std::make_unique<isa64gvzb::RiscV64GZBVecEncoding>(state);
-  decode_error_ = state->program_error_controller()->GetProgramError(
-      generic::ProgramErrorController::kInternalErrorName);
+  decoder_ = std::make_unique<RiscVGenericDecoder<
+      isa64gvzb::OpcodeEnum, isa64gvzb::RiscV64GZBVecEncoding,
+      isa64gvzb::RiscV64GVZBInstructionSet>>(
+      state, memory, riscv_encoding_.get(), riscv_isa_.get());
 }
 
-RiscV64GZBVecDecoder::~RiscV64GZBVecDecoder() { inst_db_->DecRef(); }
-
 generic::Instruction* RiscV64GZBVecDecoder::DecodeInstruction(
     uint64_t address) {
-  // First check that the address is aligned properly. If not, create and return
-  // an instruction object that will raise an exception.
-  if (address & 0x1) {
-    auto* inst = new generic::Instruction(address, state_);
-    inst->set_size(1);
-    inst->SetDisassemblyString("Misaligned instruction address");
-    inst->set_opcode(*isa64gvzb::OpcodeEnum::kNone);
-    inst->set_address(address);
-    inst->set_semantic_function([this, address](generic::Instruction* inst) {
-      state_->Trap(/*is_interrupt*/ false, address,
-                   *ExceptionCode::kInstructionAddressMisaligned, address ^ 0x1,
-                   inst);
-    });
-    return inst;
-  }
-
-  // If the address is greater than the max address, return an instruction
-  // object that will raise an exception.
-  if (address > state_->max_physical_address()) {
-    auto* inst = new generic::Instruction(address, state_);
-    inst->set_size(0);
-    inst->SetDisassemblyString("Instruction access fault");
-    inst->set_opcode(*isa64gvzb::OpcodeEnum::kNone);
-    inst->set_address(address);
-    inst->set_semantic_function([this, address](generic::Instruction* inst) {
-      state_->Trap(/*is_interrupt*/ false, address,
-                   *ExceptionCode::kInstructionAccessFault, address, nullptr);
-    });
-    return inst;
-  }
-
-  // Read the instruction word from memory and parse it in the encoding parser.
-  memory_->Load(address, inst_db_, nullptr, nullptr);
-  const uint32_t iword = inst_db_->Get<uint32_t>(0);
-  riscv_encoding_->ParseInstruction(iword);
-
-  // Call the isa decoder to obtain a new instruction object for the instruction
-  // word that was parsed above.
-  auto* instruction = riscv_isa_->Decode(address, riscv_encoding_.get());
-  return instruction;
+  return decoder_->DecodeInstruction(address);
 }
 
 }  // namespace riscv
diff --git a/riscv/riscv64gzb_vec_decoder.h b/riscv/riscv64gzb_vec_decoder.h
index 723defb..954e56b 100644
--- a/riscv/riscv64gzb_vec_decoder.h
+++ b/riscv/riscv64gzb_vec_decoder.h
@@ -18,23 +18,19 @@
 #include <cstdint>
 #include <memory>
 
-#include "mpact/sim/generic/data_buffer.h"
 #include "mpact/sim/generic/decoder_interface.h"
 #include "mpact/sim/generic/instruction.h"
-#include "mpact/sim/generic/program_error.h"
-#include "mpact/sim/generic/type_helpers.h"
 #include "mpact/sim/util/memory/memory_interface.h"
 #include "riscv/riscv64gvzb_decoder.h"
 #include "riscv/riscv64gvzb_enums.h"
 #include "riscv/riscv64gzb_vec_encoding.h"
+#include "riscv/riscv_generic_decoder.h"
 #include "riscv/riscv_state.h"
 
 namespace mpact {
 namespace sim {
 namespace riscv {
 
-using ::mpact::sim::generic::operator*;  // NOLINT: clang-tidy false positive.
-
 // This is the factory class needed by the generated decoder. It is responsible
 // for creating the decoder for each slot instance. Since the riscv architecture
 // only has a single slot, it's a pretty simple class.
@@ -56,7 +52,6 @@
 
   RiscV64GZBVecDecoder(RiscVState* state, util::MemoryInterface* memory);
   RiscV64GZBVecDecoder() = delete;
-  ~RiscV64GZBVecDecoder() override;
 
   // This will always return a valid instruction that can be executed. In the
   // case of a decode error, the semantic function in the instruction object
@@ -69,15 +64,18 @@
     return isa64gvzb::kOpcodeNames[index];
   }
 
+  // Getter.
+  isa64gvzb::RiscV64GZBVecEncoding* riscv_encoding() const {
+    return riscv_encoding_.get();
+  }
+
  private:
   RiscVState* const state_;
   util::MemoryInterface* const memory_;
-
-  // Buffer used to load instructions from memory. Re-used for each instruction
-  // word.
-  generic::DataBuffer* const inst_db_;
-
-  std::unique_ptr<generic::ProgramError> decode_error_;
+  std::unique_ptr<RiscVGenericDecoder<isa64gvzb::OpcodeEnum,
+                                      isa64gvzb::RiscV64GZBVecEncoding,
+                                      isa64gvzb::RiscV64GVZBInstructionSet>>
+      decoder_;
   std::unique_ptr<isa64gvzb::RiscV64GZBVecEncoding> riscv_encoding_;
   std::unique_ptr<RV64GVZBIsaFactory> riscv_isa_factory_;
   std::unique_ptr<isa64gvzb::RiscV64GVZBInstructionSet> riscv_isa_;
diff --git a/riscv/riscv_generic_decoder.h b/riscv/riscv_generic_decoder.h
new file mode 100644
index 0000000..9e24d9b
--- /dev/null
+++ b/riscv/riscv_generic_decoder.h
@@ -0,0 +1,152 @@
+// 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
+//
+//     https://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.
+
+#include <cstdint>
+
+#include "absl/log/log.h"
+#include "absl/status/status.h"
+#include "mpact/sim/generic/data_buffer.h"
+#include "mpact/sim/generic/decoder_interface.h"
+#include "mpact/sim/generic/instruction.h"
+#include "mpact/sim/generic/type_helpers.h"
+#include "mpact/sim/util/memory/memory_interface.h"
+#include "riscv/riscv_csr.h"
+#include "riscv/riscv_state.h"
+
+#ifndef THIRD_PARTY_MPACT_RISCV_RISCV_GENERIC_DECODER_H_
+#define THIRD_PARTY_MPACT_RISCV_RISCV_GENERIC_DECODER_H_
+
+namespace mpact::sim::riscv {
+
+using ::mpact::sim::generic::operator*;  // NOLINT: clang-tidy false positive.
+
+using ::mpact::sim::generic::Instruction;
+
+template <typename OpcodeEnum, typename Encoding, typename Isa>
+class RiscVGenericDecoder {
+ public:
+  RiscVGenericDecoder(RiscVState* state, util::MemoryInterface* memory,
+                      Encoding* encoding, Isa* isa)
+      : state_(state), memory_(memory), encoding_(encoding), isa_(isa) {
+    auto res =
+        state_->csr_set()->GetCsr(static_cast<uint64_t>(RiscVCsrEnum::kMIsa));
+    if (res.ok()) {
+      isa_csr_ = res.value();
+    } else {
+      isa_csr_ = nullptr;
+      LOG(FATAL) << "Failed to get misa CSR: " << res.status();
+    }
+
+    // Need a data buffer to load instructions from memory. Allocate a single
+    // buffer that can be reused for each instruction word.
+    inst_db_ = state_->db_factory()->Allocate<uint32_t>(1);
+  }
+
+  ~RiscVGenericDecoder() { inst_db_->DecRef(); }
+
+  Instruction* DecodeInstruction(uint64_t address) {
+    Instruction* instruction = nullptr;
+    auto status = CheckAddress(address, instruction);
+    if (!status.ok()) {
+      LOG(FATAL) << "Decoder error check failed.";
+      return nullptr;
+    }
+    if (instruction != nullptr) return instruction;
+
+    // Read the instruction word from memory and parse it in the encoding
+    // parser.
+    memory_->Load(address, inst_db_, nullptr, nullptr);
+    uint32_t iword = inst_db_->Get<uint32_t>(0);
+    encoding_->ParseInstruction(iword);
+
+    // Call the isa decoder to obtain a new instruction object for the
+    // instruction word that was parsed above.
+    instruction = isa_->Decode(address, encoding_);
+    return instruction;
+  }
+
+ private:
+  // Check that the address is valid. If not, return an instruction object that
+  // will raise an exception.
+  absl::Status CheckAddress(uint64_t address, Instruction*& inst) {
+    if (isa_csr_ == nullptr) {
+      return absl::InvalidArgumentError("MISA CSR is null");
+    }
+    if (inst != nullptr) {
+      return absl::InvalidArgumentError("Instruction is not null");
+    }
+    // First check that the address is aligned properly. If not, create and
+    // return an instruction object that will raise an exception.
+    if (address & 0x1) {
+      inst = new generic::Instruction(0, state_);
+      inst->set_size(1);
+      inst->SetDisassemblyString("Misaligned instruction address");
+      inst->set_opcode(*OpcodeEnum::kNone);
+      inst->set_address(address);
+      inst->set_semantic_function([this](generic::Instruction* inst) {
+        state_->Trap(/*is_interrupt*/ false, inst->address(),
+                     *ExceptionCode::kInstructionAddressMisaligned,
+                     inst->address() ^ 0x1, inst);
+      });
+      return absl::OkStatus();
+    }
+
+    // If the C extension is not supported and the address is not 4 byte
+    // aligned, return an instruction that will raise an exception.
+    if (((isa_csr_->GetUint64() &
+          static_cast<uint64_t>(IsaExtensions::kCompressed)) == 0) &&
+        (address & 0x3)) {
+      inst = new generic::Instruction(0, state_);
+      inst->set_size(1);
+      inst->SetDisassemblyString("Misaligned instruction address");
+      inst->set_opcode(*OpcodeEnum::kNone);
+      inst->set_address(address);
+      inst->set_semantic_function([this](generic::Instruction* inst) {
+        state_->Trap(/*is_interrupt*/ false, inst->address(),
+                     *ExceptionCode::kInstructionAddressMisaligned,
+                     inst->address() & ~0x3, inst);
+      });
+      return absl::OkStatus();
+    }
+
+    // If the address is greater than the max address, return an instruction
+    // that will raise an exception.
+    if (address > state_->max_physical_address()) {
+      inst = new generic::Instruction(0, state_);
+      inst->set_size(0);
+      inst->SetDisassemblyString("Instruction access fault");
+      inst->set_opcode(*OpcodeEnum::kNone);
+      inst->set_address(address);
+      inst->set_semantic_function([this](generic::Instruction* inst) {
+        state_->Trap(/*is_interrupt*/ false, inst->address(),
+                     *ExceptionCode::kInstructionAccessFault, inst->address(),
+                     nullptr);
+      });
+      return absl::OkStatus();
+    }
+
+    return absl::OkStatus();
+  }
+
+  RiscVState* state_;
+  util::MemoryInterface* memory_;
+  Encoding* encoding_;
+  Isa* isa_;
+  generic::DataBuffer* inst_db_;
+  RiscVCsrInterface* isa_csr_;
+};
+
+}  // namespace mpact::sim::riscv
+
+#endif  // THIRD_PARTY_MPACT_RISCV_RISCV_GENERIC_DECODER_H_
diff --git a/riscv/riscv_state.h b/riscv/riscv_state.h
index 63c5aaa..f04d56d 100644
--- a/riscv/riscv_state.h
+++ b/riscv/riscv_state.h
@@ -48,6 +48,36 @@
 using Instruction = ::mpact::sim::generic::Instruction;
 using ReferenceCount = ::mpact::sim::generic::ReferenceCount;
 
+enum class IsaExtensions : uint64_t {
+  kAtomic = 1 << 0,
+  kBitManipulation = 1 << 1,
+  kCompressed = 1 << 2,
+  kDoublePrecisionFp = 1 << 3,
+  kEBaseIsa = 1 << 4,
+  kSinglePrecisionFp = 1 << 5,
+  kGReserved = 1 << 6,
+  kHypervisor = 1 << 7,
+  kIBaseIsa = 1 << 8,
+  kJReserved = 1 << 9,
+  kKReserved = 1 << 10,
+  kLReserved = 1 << 11,
+  kIntegerMulDiv = 1 << 12,
+  kUserLevelInterrupts = 1 << 13,
+  kOReserved = 1 << 14,
+  kPReserved = 1 << 15,
+  kQuadPrecisionFp = 1 << 16,
+  kRReserved = 1 << 17,
+  kSupervisorMode = 1 << 18,
+  kTReserved = 1 << 19,
+  kUserMode = 1 << 20,
+  kVector = 1 << 21,
+  kWReserved = 1 << 22,
+  kNonStandardExtension = 1 << 23,
+  kYReserved = 1 << 24,
+  kZReserved = 1 << 25,
+};
+
+constexpr uint64_t kCompressedExtension = 1 << 2;
 // Exception codes.
 enum class ExceptionCode : uint64_t {
   kInstructionAddressMisaligned = 0,
diff --git a/riscv/test/BUILD b/riscv/test/BUILD
index a41ea0c..3d0b397 100644
--- a/riscv/test/BUILD
+++ b/riscv/test/BUILD
@@ -475,13 +475,16 @@
     ],
     deps = [
         "//riscv:riscv32g_decoder",
+        "//riscv:riscv32g_isa",
         "//riscv:riscv_state",
         "@com_github_serge1_elfio//:elfio",
         "@com_google_absl//absl/log",
         "@com_google_absl//absl/log:check",
         "@com_google_absl//absl/strings",
         "@com_google_googletest//:gtest_main",
+        "@com_google_mpact-sim//mpact/sim/generic:core",
         "@com_google_mpact-sim//mpact/sim/generic:instruction",
+        "@com_google_mpact-sim//mpact/sim/generic:type_helpers",
         "@com_google_mpact-sim//mpact/sim/util/memory",
         "@com_google_mpact-sim//mpact/sim/util/program_loader:elf_loader",
     ],
@@ -498,6 +501,7 @@
     ],
     deps = [
         "//riscv:riscv64g_decoder",
+        "//riscv:riscv64g_isa",
         "//riscv:riscv_state",
         "@com_github_serge1_elfio//:elfio",
         "@com_google_absl//absl/log",
@@ -505,7 +509,9 @@
         "@com_google_absl//absl/log:log_sink_registry",
         "@com_google_absl//absl/strings",
         "@com_google_googletest//:gtest_main",
+        "@com_google_mpact-sim//mpact/sim/generic:core",
         "@com_google_mpact-sim//mpact/sim/generic:instruction",
+        "@com_google_mpact-sim//mpact/sim/generic:type_helpers",
         "@com_google_mpact-sim//mpact/sim/util/memory",
         "@com_google_mpact-sim//mpact/sim/util/other:log_sink",
         "@com_google_mpact-sim//mpact/sim/util/program_loader:elf_loader",
diff --git a/riscv/test/riscv32_bitmanip_instructions_test.cc b/riscv/test/riscv32_bitmanip_instructions_test.cc
index a679ba3..0b526d6 100644
--- a/riscv/test/riscv32_bitmanip_instructions_test.cc
+++ b/riscv/test/riscv32_bitmanip_instructions_test.cc
@@ -16,7 +16,6 @@
 #include <cstdint>
 #include <ios>
 #include <limits>
-#include <new>
 #include <string>
 #include <tuple>
 #include <vector>
diff --git a/riscv/test/riscv32_decoder_test.cc b/riscv/test/riscv32_decoder_test.cc
index a0d2d91..282238d 100644
--- a/riscv/test/riscv32_decoder_test.cc
+++ b/riscv/test/riscv32_decoder_test.cc
@@ -25,14 +25,24 @@
 #include "elfio/elfio_section.hpp"
 #include "elfio/elfio_symbols.hpp"
 #include "googlemock/include/gmock/gmock.h"
+#include "mpact/sim/generic/data_buffer.h"
 #include "mpact/sim/generic/instruction.h"
+#include "mpact/sim/generic/type_helpers.h"
 #include "mpact/sim/util/memory/flat_demand_memory.h"
 #include "mpact/sim/util/program_loader/elf_program_loader.h"
+#include "riscv/riscv32g_enums.h"
+#include "riscv/riscv_csr.h"
 #include "riscv/riscv_state.h"
 
 namespace {
 
+using ::mpact::sim::generic::operator*;  // NOLINT: clang-tidy false positive.
+using ::mpact::sim::riscv::IsaExtensions;
+using ::mpact::sim::riscv::RiscVCsrEnum;
 using ::mpact::sim::riscv::RiscVXlen;
+using ::mpact::sim::riscv::isa32::OpcodeEnum;
+
+constexpr uint16_t kCNopOpcode = 0b000'0'00000'00000'01;
 
 constexpr char kFileName[] = "hello_world.elf";
 
@@ -64,6 +74,7 @@
   mpact::sim::riscv::RiscVState state_;
   mpact::sim::util::ElfProgramLoader loader_;
   mpact::sim::riscv::RiscV32Decoder decoder_;
+  mpact::sim::generic::DataBufferFactory db_factory_;
   SymbolAccessor* symbol_accessor_;
 };
 
@@ -113,4 +124,29 @@
   inst->DecRef();
 }
 
+TEST_F(RiscV32DecoderTest, BadAddressNoCompressedInstructions) {
+  // Write opcode to memory.
+  auto* db = db_factory_.Allocate<uint16_t>(1);
+  db->Set<uint16_t>(0, kCNopOpcode);
+  memory_.Store(0x4322, db);
+  db->DecRef();
+  // Get a handle to the MISA CSR.
+  auto res = state_.csr_set()->GetCsr(*RiscVCsrEnum::kMIsa);
+  CHECK_OK(res.status());
+  auto* misa_csr = res.value();
+  misa_csr->Set(misa_csr->GetUint64() | *IsaExtensions::kCompressed);
+  // If the C bit is set, then the decoder should succeed.
+  auto* inst = decoder_.DecodeInstruction(0x4322);
+  ASSERT_NE(inst, nullptr);
+  EXPECT_EQ(inst->opcode(), static_cast<uint32_t>(OpcodeEnum::kCnop));
+  inst->DecRef();
+  misa_csr->Set(misa_csr->GetUint64() & ~*IsaExtensions::kCompressed);
+  // If the C bit is not set, then the decoder should generate an invalid
+  // address exception.
+  inst = decoder_.DecodeInstruction(0x4322);
+  ASSERT_NE(inst, nullptr);
+  EXPECT_EQ(inst->opcode(), static_cast<uint32_t>(OpcodeEnum::kNone));
+  inst->DecRef();
+}
+
 }  // namespace
diff --git a/riscv/test/riscv64_decoder_test.cc b/riscv/test/riscv64_decoder_test.cc
index e4461a5..41f3911 100644
--- a/riscv/test/riscv64_decoder_test.cc
+++ b/riscv/test/riscv64_decoder_test.cc
@@ -26,19 +26,29 @@
 #include "elfio/elfio_section.hpp"
 #include "elfio/elfio_symbols.hpp"
 #include "googlemock/include/gmock/gmock.h"
+#include "mpact/sim/generic/data_buffer.h"
 #include "mpact/sim/generic/instruction.h"
+#include "mpact/sim/generic/type_helpers.h"
 #include "mpact/sim/util/memory/flat_demand_memory.h"
 #include "mpact/sim/util/other/log_sink.h"
 #include "mpact/sim/util/program_loader/elf_program_loader.h"
+#include "riscv/riscv64g_enums.h"
+#include "riscv/riscv_csr.h"
 #include "riscv/riscv_state.h"
 
 namespace {
 
+using ::mpact::sim::generic::operator*;  // NOLINT: clang-tidy false positive.
+using ::mpact::sim::riscv::IsaExtensions;
 using ::mpact::sim::riscv::RiscV64Decoder;
+using ::mpact::sim::riscv::RiscVCsrEnum;
 using ::mpact::sim::riscv::RiscVState;
 using ::mpact::sim::riscv::RiscVXlen;
+using ::mpact::sim::riscv::isa64::OpcodeEnum;
 using ::mpact::sim::util::LogSink;
 
+constexpr uint16_t kCNopOpcode = 0b000'0'00000'00000'01;
+
 constexpr char kFileName[] = "hello_world_64.elf";
 
 // The depot path to the test directory.
@@ -69,6 +79,7 @@
   mpact::sim::util::FlatDemandMemory memory_;
   mpact::sim::util::ElfProgramLoader loader_;
   RiscV64Decoder decoder_;
+  mpact::sim::generic::DataBufferFactory db_factory_;
   SymbolAccessor* symbol_accessor_;
 };
 
@@ -128,4 +139,29 @@
   inst->DecRef();
 }
 
+TEST_F(RiscV64DecoderTest, BadAddressNoCompressedInstructions) {
+  // Write opcode to memory.
+  auto* db = db_factory_.Allocate<uint16_t>(1);
+  db->Set<uint16_t>(0, kCNopOpcode);
+  memory_.Store(0x4322, db);
+  db->DecRef();
+  // Get a handle to the MISA CSR.
+  auto res = state_.csr_set()->GetCsr(*RiscVCsrEnum::kMIsa);
+  CHECK_OK(res.status());
+  auto* misa_csr = res.value();
+  misa_csr->Set(misa_csr->GetUint64() | *IsaExtensions::kCompressed);
+  // If the C bit is set, then the decoder should succeed.
+  auto* inst = decoder_.DecodeInstruction(0x4322);
+  ASSERT_NE(inst, nullptr);
+  EXPECT_EQ(inst->opcode(), static_cast<uint32_t>(OpcodeEnum::kCnop));
+  inst->DecRef();
+  misa_csr->Set(misa_csr->GetUint64() & ~*IsaExtensions::kCompressed);
+  // If the C bit is not set, then the decoder should generate an invalid
+  // address exception.
+  inst = decoder_.DecodeInstruction(0x4322);
+  ASSERT_NE(inst, nullptr);
+  EXPECT_EQ(inst->opcode(), static_cast<uint32_t>(OpcodeEnum::kNone));
+  inst->DecRef();
+}
+
 }  // namespace