This refactors the compact instructions and adds new ones defined in RVM23.

PiperOrigin-RevId: 693795340
Change-Id: Ia8e1a9abe343c85873ba1916ea24b58d94125cd8
diff --git a/riscv/BUILD b/riscv/BUILD
index 67b9439..276a42e 100644
--- a/riscv/BUILD
+++ b/riscv/BUILD
@@ -72,6 +72,7 @@
     hdrs = [
         "riscv_counter_csr.h",
         "riscv_csr.h",
+        "riscv_jvt.h",
         "riscv_misa.h",
         "riscv_pmp.h",
         "riscv_register.h",
@@ -253,14 +254,16 @@
 )
 
 cc_library(
-    name = "riscv_rvm23_instructions",
+    name = "rvm23_instructions",
     srcs = [
+        "riscv_zc_instructions.cc",
         "riscv_zhintpause_instructions.cc",
         "riscv_zicond_instructions.cc",
         "riscv_zihintntl_instructions.cc",
         "riscv_zimop_instructions.cc",
     ],
     hdrs = [
+        "riscv_zc_instructions.h",
         "riscv_zhintpause_instructions.h",
         "riscv_zicond_instructions.h",
         "riscv_zihintntl_instructions.h",
@@ -372,6 +375,7 @@
         "riscv32v.isa",
         "riscv32zb.isa",
         "riscv_vector.isa",
+        "riscv_zc.isa",
         "riscv_zhintpause.isa",
         "riscv_zicond.isa",
         "riscv_zihintntl.isa",
@@ -396,6 +400,7 @@
         "riscv32g.bin_fmt",
         "riscv32v.bin_fmt",
         "riscv32zb.bin_fmt",
+        "riscv_zc.bin_fmt",
         "riscv_zhintpause.bin_fmt",
         "riscv_zicond.bin_fmt",
         "riscv_zihintntl.bin_fmt",
diff --git a/riscv/riscv32g.isa b/riscv/riscv32g.isa
index 72c368e..10fcc09 100644
--- a/riscv/riscv32g.isa
+++ b/riscv/riscv32g.isa
@@ -764,7 +764,7 @@
       disasm: "jal", "0x%(@+I_cj_imm11:08x)",
       semfunc: "&RV32::RiscVIJal";
     cjr{: crs1, x0 : next_pc, x0},
-      resources: {next_pc,crs1, x0 : next_pc[0..], x0[0..]},
+      resources: {next_pc, crs1, x0 : next_pc[0..], x0[0..]},
       disasm: "jr", "%crs1",
       semfunc: "&RV32::RiscVIJalr";
     cjalr{: crs1, x0 : next_pc, x1},
diff --git a/riscv/riscv_b_instructions.cc b/riscv/riscv_b_instructions.cc
index afcadcb..9f0e212 100644
--- a/riscv/riscv_b_instructions.cc
+++ b/riscv/riscv_b_instructions.cc
@@ -15,6 +15,7 @@
 #include "riscv/riscv_b_instructions.h"
 
 #include <algorithm>
+#include <cstdint>
 #include <type_traits>
 
 #include "absl/base/casts.h"
@@ -22,6 +23,7 @@
 #include "absl/types/span.h"
 #include "mpact/sim/generic/instruction.h"
 #include "riscv/riscv_instruction_helpers.h"
+#include "riscv/riscv_register.h"
 
 namespace mpact {
 namespace sim {
diff --git a/riscv/riscv_bitmanip_instructions.cc b/riscv/riscv_bitmanip_instructions.cc
index f7c8468..0084402 100644
--- a/riscv/riscv_bitmanip_instructions.cc
+++ b/riscv/riscv_bitmanip_instructions.cc
@@ -63,6 +63,11 @@
       instruction, [](UIntReg a, UIntReg b) { return ~(a ^ b); });
 }
 
+void RiscVNot(const generic::Instruction *instruction) {
+  RiscVUnaryOp<RegisterType, UIntReg, UIntReg>(instruction,
+                                               [](UIntReg a) { return ~a; });
+}
+
 // Count leading zeros.
 void RiscVClz(const Instruction *instruction) {
   RiscVUnaryOp<RegisterType, UIntReg, UIntReg>(
@@ -124,6 +129,13 @@
       [](uint16_t a) -> UIntReg { return static_cast<UIntReg>(a); });
 }
 
+// Zero extend byte.
+void RiscVZextB(const Instruction *instruction) {
+  RiscVUnaryOp<RegisterType, UIntReg, uint8_t>(
+      instruction,
+      [](uint8_t a) -> UIntReg { return static_cast<UIntReg>(a); });
+}
+
 // Rotate left.
 void RiscVRol(const Instruction *instruction) {
   RiscVBinaryOp<RegisterType, UIntReg, UIntReg>(
@@ -290,6 +302,11 @@
       instruction, [](UIntReg a, UIntReg b) { return ~(a ^ b); });
 }
 
+void RiscVNot(const generic::Instruction *instruction) {
+  RiscVUnaryOp<RegisterType, UIntReg, UIntReg>(instruction,
+                                               [](UIntReg a) { return ~a; });
+}
+
 // Count leading zeros.
 void RiscVClz(const Instruction *instruction) {
   RiscVUnaryOp<RegisterType, UIntReg, UIntReg>(
@@ -366,6 +383,13 @@
       [](uint16_t a) -> UIntReg { return static_cast<UIntReg>(a); });
 }
 
+// Zero extend byte.
+void RiscVZextB(const Instruction *instruction) {
+  RiscVUnaryOp<RegisterType, UIntReg, uint8_t>(
+      instruction,
+      [](uint8_t a) -> UIntReg { return static_cast<UIntReg>(a); });
+}
+
 // Rotate left.
 void RiscVRol(const Instruction *instruction) {
   RiscVBinaryOp<RegisterType, UIntReg, UIntReg>(
diff --git a/riscv/riscv_bitmanip_instructions.h b/riscv/riscv_bitmanip_instructions.h
index 3b346de..65d2d34 100644
--- a/riscv/riscv_bitmanip_instructions.h
+++ b/riscv/riscv_bitmanip_instructions.h
@@ -34,6 +34,10 @@
 void RiscVAndn(const Instruction *instruction);
 void RiscVOrn(const Instruction *instruction);
 void RiscVXnor(const Instruction *instruction);
+// Performs the bitwise negation of rs1 (this instruction is strictly not part
+// of Zbb, but part of the 16 bit Zcb extension). It is implemented here because
+// it fits with the other bitwise instructions.
+void RiscVNot(const generic::Instruction *instruction);
 // These functions take 1 source operands, rs1, and one destination operand rd.
 void RiscVClz(const Instruction *instruction);
 void RiscVCtz(const Instruction *instruction);
@@ -47,6 +51,7 @@
 // These functions take 1 source operands, rs1, and one destination operand rd.
 void RiscVSextB(const Instruction *instruction);
 void RiscVSextH(const Instruction *instruction);
+void RiscVZextB(const Instruction *instruction);
 void RiscVZextH(const Instruction *instruction);
 // These functions take 2 source operands, rs1, rs2, and one destination operand
 // rd.
diff --git a/riscv/riscv_csr.h b/riscv/riscv_csr.h
index 01dec93..54d5207 100644
--- a/riscv/riscv_csr.h
+++ b/riscv/riscv_csr.h
@@ -52,6 +52,8 @@
   kVxsat = 0x009,
   kVxrm = 0x00a,
   kVcsr = 0x00f,
+  // Jump base vector and control register.
+  kJvt = 0x017,
   // User trap handling.
   kUScratch = 0x040,
   kUEpc = 0x041,
diff --git a/riscv/riscv_getter_helpers.h b/riscv/riscv_getter_helpers.h
index e4cb71f..dc3f91d 100644
--- a/riscv/riscv_getter_helpers.h
+++ b/riscv/riscv_getter_helpers.h
@@ -16,6 +16,7 @@
 #define THIRD_PARTY_MPACT_RISCV_RISCV_GETTER_HELPERS_H_
 
 #include <string>
+#include <vector>
 
 #include "absl/container/flat_hash_map.h"
 #include "absl/functional/any_invocable.h"
@@ -39,9 +40,13 @@
 
 using SourceOpGetterMap =
     absl::flat_hash_map<int, absl::AnyInvocable<SourceOperandInterface *()>>;
+using ListSourceOpGetterMap = absl::flat_hash_map<
+    int, absl::AnyInvocable<std::vector<SourceOperandInterface *>()>>;
 using DestOpGetterMap =
     absl::flat_hash_map<int,
                         absl::AnyInvocable<DestinationOperandInterface *(int)>>;
+using ListDestOpGetterMap = absl::flat_hash_map<
+    int, absl::AnyInvocable<std::vector<DestinationOperandInterface *>(int)>>;
 using SimpleResourceGetterMap =
     absl::flat_hash_map<int, absl::AnyInvocable<generic::SimpleResource *()>>;
 using ComplexResourceGetterMap = absl::flat_hash_map<
@@ -51,7 +56,11 @@
 // the riscv_*_getter.h files.
 template <typename M, typename E, typename G>
 inline void Insert(M &map, E entry, G getter) {
-  map.insert(std::make_pair(static_cast<int>(entry), getter));
+  if (!map.contains(static_cast<int>(entry))) {
+    map.insert(std::make_pair(static_cast<int>(entry), getter));
+  } else {
+    map.at(static_cast<int>(entry)) = getter;
+  }
 }
 
 // Generic helper functions to create register operands.
diff --git a/riscv/riscv_getters_rv32.h b/riscv/riscv_getters_rv32.h
index 3f51eab..0283882 100644
--- a/riscv/riscv_getters_rv32.h
+++ b/riscv/riscv_getters_rv32.h
@@ -57,13 +57,13 @@
     auto num = Extractors::CS::ExtractCsRs2(common->inst_word());
     return GetRegisterSourceOp<FpRegister>(
         common->state(), absl::StrCat(RiscVState::kFregPrefix, num),
-        kXRegisterAliases[num]);
+        kFRegisterAliases[num]);
   });
   Insert(getter_map, *Enum::kCfrs2, [common]() {
     auto num = Extractors::CR::ExtractRs2(common->inst_word());
     return GetRegisterSourceOp<FpRegister>(
         common->state(), absl::StrCat(RiscVState::kFregPrefix, num),
-        kXRegisterAliases[num]);
+        kFRegisterAliases[num]);
   });
 }
 
diff --git a/riscv/riscv_instruction_helpers.h b/riscv/riscv_instruction_helpers.h
index 7827b78..db40dc9 100644
--- a/riscv/riscv_instruction_helpers.h
+++ b/riscv/riscv_instruction_helpers.h
@@ -308,6 +308,17 @@
   reg->data_buffer()->template Set<Result>(0, dest_value);
 }
 
+// Generic helper function for writing a value to a register by destination
+// operand index.
+template <typename Register, typename Value>
+inline void RiscVWriteReg(const Instruction *instruction, int index,
+                          Value value) {
+  auto *reg = static_cast<generic::RegisterDestinationOperand<Value> *>(
+                  instruction->Destination(index))
+                  ->GetRegister();
+  reg->data_buffer()->template Set<Value>(0, value);
+}
+
 // Generic helper function for unary instructions.
 template <typename Register, typename Result, typename Argument>
 inline void RiscVUnaryOp(const Instruction *instruction,
diff --git a/riscv/riscv_jvt.h b/riscv/riscv_jvt.h
new file mode 100644
index 0000000..0e89765
--- /dev/null
+++ b/riscv/riscv_jvt.h
@@ -0,0 +1,42 @@
+// Copyright 2023 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.
+
+#ifndef THIRD_PARTY_MPACT_RISCV_RISCV_JVT_H_
+#define THIRD_PARTY_MPACT_RISCV_RISCV_JVT_H_
+
+#include <cstdint>
+#include <string>
+
+#include "riscv/riscv_csr.h"
+#include "riscv/riscv_state.h"
+
+namespace mpact::sim::riscv {
+
+template <typename T>
+class RiscVJvtCsr : public RiscVSimpleCsr<T> {
+ public:
+  RiscVJvtCsr(std::string name, RiscVCsrEnum index, T initial_value,
+              RiscVState *state)
+      : RiscVSimpleCsr<T>(name, index, initial_value, state) {}
+  // Clear the low 6 bits of the value (sets mode to 0b00'0000). This will be
+  // modified as any new modes are added.
+  void Write(uint32_t value) override { this->Set(value & ~0x3f); }
+  void Write(uint64_t value) override {
+    this->Set(static_cast<uint64_t>(value & ~0x3fULL));
+  }
+};
+
+}  // namespace mpact::sim::riscv
+
+#endif  // THIRD_PARTY_MPACT_RISCV_RISCV_JVT_H_
diff --git a/riscv/riscv_state.cc b/riscv/riscv_state.cc
index 0d489fc..7eebd76 100644
--- a/riscv/riscv_state.cc
+++ b/riscv/riscv_state.cc
@@ -29,6 +29,7 @@
 #include "mpact/sim/util/memory/memory_interface.h"
 #include "riscv/riscv_counter_csr.h"
 #include "riscv/riscv_csr.h"
+#include "riscv/riscv_jvt.h"
 #include "riscv/riscv_misa.h"
 #include "riscv/riscv_pmp.h"
 #include "riscv/riscv_register.h"
@@ -304,6 +305,12 @@
   state->pmp_ = new RiscVPmp(state);
   state->pmp_->CreatePmpCsrs<T, RiscVCsrEnum>(state->csr_set());
 
+  // Jump base vector and control register (for Zcmt instructions).
+  auto *jvt_csr = CreateCsr<RiscVJvtCsr<T>>(state, state->jvt_, csr_vec, "jvt",
+                                            RiscVCsrEnum::kJvt, 0, state);
+  CHECK_NE(jvt_csr, nullptr);
+  state->jvt_ = jvt_csr;
+
   // Simulator CSRs
 
   // Access current privilege mode.
diff --git a/riscv/riscv_state.h b/riscv/riscv_state.h
index b9b48e8..97aa718 100644
--- a/riscv/riscv_state.h
+++ b/riscv/riscv_state.h
@@ -377,6 +377,7 @@
   RiscVMIsa *misa() const { return misa_; }
   RiscVMIp *mip() const { return mip_; }
   RiscVMIe *mie() const { return mie_; }
+  RiscVCsrInterface *jvt() const { return jvt_; }
   RiscVCsrInterface *mtvec() const { return mtvec_; }
   RiscVCsrInterface *mepc() const { return mepc_; }
   RiscVCsrInterface *mcause() const { return mcause_; }
@@ -426,6 +427,7 @@
   RiscVMIp *mip_ = nullptr;
   RiscVMIe *mie_ = nullptr;
   RiscVPmp *pmp_ = nullptr;
+  RiscVCsrInterface *jvt_ = nullptr;
   RiscVCsrInterface *mtvec_ = nullptr;
   RiscVCsrInterface *mepc_ = nullptr;
   RiscVCsrInterface *mcause_ = nullptr;
diff --git a/riscv/riscv_zc.bin_fmt b/riscv/riscv_zc.bin_fmt
new file mode 100644
index 0000000..76c3ea9
--- /dev/null
+++ b/riscv/riscv_zc.bin_fmt
@@ -0,0 +1,325 @@
+// Copyright 2024 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.
+
+// This file refactors the original "C" extension into the new set of Zc*
+// extensions. These should be preferred for new simulator targets.
+
+// Compact instruction formats.
+
+format Inst16Format[16] {
+  fields:
+    unsigned func3[3];
+    unsigned bits[11];
+    unsigned op[2];
+};
+
+format CA[16] : Inst16Format {
+  fields:
+    unsigned func6[6];
+    unsigned rs1p[3];
+    unsigned func2[2];
+    unsigned fs2p[3];
+    unsigned op[2];
+  overlays:
+    unsigned uimm2[2] = func2;
+    unsigned uimm1[1] = func2[0];
+    unsigned func5[5] = func2, fs2p;
+    unsigned rs1[5] = 0b01, rs1p;
+    unsigned rs2[5] = 0b01, fs2p;
+    unsigned rd[5] = 0b01, rs1p;
+};
+
+format CB[16] : Inst16Format {
+  fields:
+    unsigned func3[3];
+    unsigned imm3[3];
+    unsigned rs1p[3];
+    unsigned imm5[5];
+    unsigned op[2];
+  overlays:
+    unsigned func2[2] = [11, 10];
+    unsigned func5[5] = [12..10, 6..5];
+    unsigned shamt[6] = [12, 6..2];
+    unsigned rs2p[3] = [4..2];
+    unsigned rs2[5] = 0b10, [4..2];
+    signed bimm[9] = imm3[2], imm5[4..3, 0], imm3[1..0], imm5[2..1], 0b0;
+};
+
+format CI[16] : Inst16Format {
+  fields:
+    unsigned func3[3];
+    unsigned imm1[1];
+    unsigned rs1[5];
+    unsigned imm5[5];
+    unsigned op[2];
+  overlays:
+    unsigned rd[5] = rs1;
+    signed imm6[6] = imm1, imm5;
+    unsigned uimm6[6] = imm1, imm5;
+    signed imm18[18] = imm1, imm5, 0b0000'0000'0000;
+    signed ci_imm10[10] = imm1, imm5[2..1, 3, 0, 4], 0b0000;
+    unsigned ci_imm_w[8] = imm5[1..0], imm1, imm5[4..2], 0b00;
+    unsigned ci_imm_d[9] = imm5[2..0], imm1, imm5[4..3], 0b000;
+};
+
+format CIW[16] : Inst16Format {
+  fields:
+    unsigned func3[3];
+    unsigned imm8[8];
+    unsigned rdp[3];
+    unsigned op[2];
+  overlays:
+    unsigned rd[5] = 0b01, rdp;
+    unsigned ciw_imm10[10] = imm8[5..2, 7..6, 0, 1], 0b00;
+};
+
+format CJ[16] : Inst16Format {
+  fields:
+    unsigned func3[3];
+    unsigned imm11[11];
+    unsigned op[2];
+  overlays:
+    signed jimm[12] = imm11[10, 6, 8..7, 4, 5, 0, 9, 3..1], 0b0;
+};
+
+format CL[16] : Inst16Format {
+  fields:
+    unsigned func3[3];
+    unsigned imm3[3];
+    unsigned rs1p[3];
+    unsigned imm2[2];
+    unsigned rdp[3];
+    unsigned op[2];
+  overlays:
+    unsigned cl_rs1[5] = 0b01, rs1p;
+    unsigned cl_rd[5] = 0b01, rdp;
+    unsigned cl_imm_w[7] = imm2[0], imm3, imm2[1], 0b00;
+    unsigned cl_imm_d[8] = imm2, imm3, 0b000;
+};
+
+format CLB[16] : Inst16Format {
+  fields:
+    unsigned func6[6];
+    unsigned rs1p[3];
+    unsigned uimm2[2];
+    unsigned rdp[3];
+    unsigned op[2];
+  overlays:
+    unsigned rs1[5] = 0b01, rs1p;
+    unsigned rd[5] = 0b01, rdp;
+};
+
+format CLH[16] : Inst16Format {
+  fields:
+    unsigned func6[6];
+    unsigned rs1p[3];
+    unsigned func1[1];
+    unsigned uimm1[1];
+    unsigned rdp[3];
+    unsigned op[2];
+  overlays:
+    unsigned rs1[5] = 0b01, rs1p;
+    unsigned rd[5] = 0b01, rdp;
+    unsigned uimm2[2] = uimm1, 0b0;
+};
+
+format CMJT[16] : Inst16Format {
+  fields:
+    unsigned func6[6];
+    unsigned index[8];
+    unsigned op[2];
+  overlays:
+};
+
+format CMMV[16] : Inst16Format {
+  fields:
+    unsigned func6[6];
+    unsigned rs1p[3];
+    unsigned func2[2];
+    unsigned rs2p[3];
+    unsigned op[2];
+  overlays:
+    unsigned rs1[5] = 0b01, rs1p;
+    unsigned rs2[5] = 0b01, rs2p;
+};
+
+format CMPP[16] : Inst16Format {
+  fields:
+    unsigned func6[6];
+    unsigned func2[2];
+    unsigned rlist[4];
+    unsigned spimm[2];
+    unsigned op[2];
+};
+
+format CR[16] : Inst16Format {
+  fields:
+    unsigned func4[4];
+    unsigned rs1[5];
+    unsigned rs2[5];
+    unsigned op[2];
+};
+
+format CS[16] : Inst16Format {
+  fields:
+    unsigned func3[3];
+    unsigned imm3[3];
+    unsigned rs1p[3];
+    unsigned imm2[2];
+    unsigned rs2p[3];
+    unsigned op[2];
+  overlays:
+    unsigned cs_rs1[5] = 0b01, rs1p;
+    unsigned cs_rs2[5] = 0b01, rs2p;
+    unsigned cs_imm_w[7] = imm2[0], imm3, imm2[1], 0b00;
+    unsigned cs_imm_d[8] = imm2, imm3, 0b000;
+};
+
+format CSB[16] : Inst16Format {
+  fields:
+    unsigned func6[6];
+    unsigned rs1p[3];
+    unsigned uimm2[2];
+    unsigned rs2p[3];
+    unsigned op[2];
+  overlays:
+    unsigned rs1[5] = 0b01, rs1p;
+    unsigned rs2[5] = 0b01, rs2p;
+};
+
+format CSH[16] : Inst16Format {
+  fields:
+    unsigned func6[6];
+    unsigned rs1p[3];
+    unsigned func1[1];
+    unsigned uimm1[1];
+    unsigned rs2p[3];
+    unsigned op[2];
+  overlays:
+    unsigned rs1[5] = 0b01, rs1p;
+    unsigned rs2[5] = 0b01, rs2p;
+    unsigned uimm2[2] = uimm1, 0b0;
+};
+
+format CSS[16] : Inst16Format {
+  fields:
+    unsigned func3[3];
+    unsigned imm6[6];
+    unsigned rs2[5];
+    unsigned op[2];
+  overlays:
+    unsigned css_imm_w[8] = imm6[1..0], imm6[5..2], 0b00;
+    unsigned css_imm_d[9] = imm6[2..0], imm6[5..3], 0b000;
+};
+
+format CU[16] : Inst16Format {
+  fields:
+    unsigned func6[6];
+    unsigned rs1p[3];
+    unsigned func5[5];
+    unsigned op[2];
+  overlays:
+    unsigned rs1[5] = 0b01, rs1p;
+    unsigned rd[5] = 0b01, rs1p;
+};
+
+// Non floating point compact instructions from "C".
+instruction group RiscVZca[16] : Inst16Format {
+  caddi4spn : CIW: func3 == 0b000, op == 0b00, imm8 != 0;
+  clw       : CL : func3 == 0b010, op == 0b00;
+  csw       : CS : func3 == 0b110, op == 0b00;
+  cnop      : CI : func3 == 0b000, imm1 == 0, rs1 == 0, imm5 == 0, op == 0b01;
+  caddi     : CI : func3 == 0b000, imm6 != 0, rd != 0, op == 0b01;
+  cjal      : CJ : func3 == 0b001, op == 0b01;
+  cli       : CI : func3 == 0b010, rd != 0, op == 0b01;
+  caddi16sp : CI : func3 == 0b011, rd == 2, op == 0b01;
+  clui      : CI : func3 == 0b011, rd != 0, rd != 2, op == 0b01;
+  csrli     : CB : func3 == 0b100, imm3 == 0b000, op == 0b01;
+  csrai     : CB : func3 == 0b100, imm3 == 0b001, op == 0b01;
+  candi     : CB : func3 == 0b100, func2 == 0b10, op == 0b01;
+  csub      : CA : func6 == 0b100'011, func2 == 0b00, op == 0b01;
+  cxor      : CA : func6 == 0b100'011, func2 == 0b01, op == 0b01;
+  cor       : CA : func6 == 0b100'011, func2 == 0b10, op == 0b01;
+  cand      : CA : func6 == 0b100'011, func2 == 0b11, op == 0b01;
+  cj        : CJ : func3 == 0b101, op == 0b01;
+  cbeqz     : CB : func3 == 0b110, op == 0b01;
+  cbnez     : CB : func3 == 0b111, op == 0b01;
+  cslli     : CI : func3 == 0b000, imm1 == 0, rs1 != 0, op == 0b10;
+  clwsp     : CI : func3 == 0b010, rd != 0, op == 0b10;
+  cjr       : CR : func4 == 0b1000, rs1 != 0, rs2 == 0, op == 0b10;
+  cmv       : CR : func4 == 0b1000, rs1 != 0, rs2 != 0, op == 0b10;
+  cebreak   : CR : func4 == 0b1001, rs1 == 0, rs2 == 0, op == 0b10;
+  cjalr     : CR : func4 == 0b1001, rs1 != 0, rs2 == 0, op == 0b10;
+  cadd      : CR : func4 == 0b1001, rs1 != 0, rs2 != 0, op == 0b10;
+  cswsp     : CSS: func3 == 0b110, op == 0b10;
+}
+
+// Single-precision floating point compact instructions from "C".
+instruction group RiscVZcf[16] : Inst16Format {
+  cflw      : CL : func3 == 0b011, op == 0b00;
+  cfsw      : CS : func3 == 0b111, op == 0b00;
+  cflwsp    : CI : func3 == 0b011, op == 0b10;
+  cfswsp    : CSS: func3 == 0b111, op == 0b10;
+}
+
+// Double-precision floating point compact instructions from "C".
+instruction group RiscVZcd[16] : Inst16Format {
+  cfld      : CL : func3 == 0b001, op == 0b00;
+  cfldsp    : CI : func3 == 0b001, op == 0b10;
+  cfsd      : CS : func3 == 0b101, op == 0b00;
+  cfsdsp    : CSS: func3 == 0b101, op == 0b10;
+}
+
+// Simple code-size saving instructions that are easy to implement on all CPUs.
+instruction group RiscVZcb32[16] : Inst16Format {
+  c_lbu    : CLB : func6 == 0b100'000, op == 0b00;
+  c_lhu    : CLH : func6 == 0b100'001, func2[1] == 0b0, op == 0b00;
+  c_lh     : CLH : func6 == 0b100'001, func2[1] == 0b1, op == 0b00;
+  c_sb     : CSB : func6 == 0b100'010, op2 == 0b00;
+  c_sh     : CSH : func6 == 0b100'011, func2[1] == 0b0, op == 0b00;
+  c_zext_b : CU : func6 == 0b100'111, func5 == 0b11000, op = 0b01;
+  c_sext_b : CU : func6 == 0b100'111, func5 == 0b11001, op == 0b01;
+  c_zext_h : CU : func6 == 0b100'111, func5 == 0b11010, op == 0b01;
+  c_sext_h : CU : func6 == 0b100'111, func5 == 0b11011, op == 0b01;
+  c_not    : CU : func6 == 0b100'111, func5 == 0b11101, op == 0b01;;
+  c_mul    : CA : func6 == 0b100'111, func2 == 0b10, op == 0b01;
+}
+
+// For 64 bit CPUs the zext_w instruction is added.
+instruction group RiscV64Zcb64[64] : Inst16Format {
+  c_zext_w : CU : func6 == 0b100'111, func5 = 0b11100, op == 0b01;
+}
+
+// Push/pop/register move instructions. Incompatible with Zcf and Zcd.
+instruction group RiscVZcmp[16] : Inst16Format {
+  cm_push    : CMPP : func8 == 0b101'11000, rlist > 3, op == 0b10;
+  cm_pop     : CMPP : func8 == 0b101'11010, rlist > 3,op == 0b10;
+  cm_popret  : CMPP : func8 == 0b101'11110, rlist > 3,op == 0b10;
+  cm_popretz : CMPP : func8 == 0b101'11100, rlist > 3, op == 0b10;
+  cm_mvsa01  : CMMV : func6 == 0b101'011, func2 == 0b01, rs1p != rs2p, op == 0b10;
+  cm_mva01s  : CMMV : func6 == 0b101'011, func2 == 0b11, op == 0b10;
+}
+
+format CMJT[16] : Inst16Format {
+  fields:
+    unsigned func6[6];
+    unsigned index[8];
+    unsigned op[2];
+}
+
+// Jump table instructions.
+instruction group RiscVZcmt[16] : Inst16Format {
+  cm_jt   : CMJT : func6 == 0b101'000, index < 32, op == 0b10;
+  cm_jalt : CMJT : func6 == 0b101'000, index >= 32,op == 0b10;
+}
\ No newline at end of file
diff --git a/riscv/riscv_zc.isa b/riscv/riscv_zc.isa
new file mode 100644
index 0000000..e386ae4
--- /dev/null
+++ b/riscv/riscv_zc.isa
@@ -0,0 +1,416 @@
+// Copyright 2024 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.
+
+// This file refactors the original "C" extension into the new set of Zc*
+// extensions. These should be preferred for new simulator targets.
+
+// Non floating point compact instructions from "C".
+
+slot riscv_zca32 {
+  default size = 2;
+  includes {
+    #include "riscv/riscv_i_instructions.h"
+  }
+  opcodes {
+    clwsp{(: x2, I_ci_uimm6x4 : ), (: : rd)},
+      resources:{next_pc,x2 : rd[0..]},
+      disasm: "lw", "%rd, %I_ci_uimm6x4(%x2)",
+      semfunc: "&RV32::RiscVILw", "&RV32::RiscVILwChild";
+    cswsp{: x2, I_css_uimm6x4, crs2 : },
+      resources: {next_pc,x2, crs2},
+      disasm: "sw", "%crs2, %I_css_uimm6x4(%x2)",
+      semfunc: "&RV32::RiscVISw";
+    clw{(: c3rs1, I_cl_uimm5x4 : ), (: : c3rd)},
+      resources: {next_pc,c3rs1 : c3rd[0..]},
+      disasm: "lw", "%c3rd, %I_cl_uimm5x4(%c3rs1)",
+      semfunc: "&RV32::RiscVILw", "&RV32::RiscVILwChild";
+    csw{: c3rs1, I_cl_uimm5x4, c3rs2 : },
+      resources: {next_pc,c3rs1, c3rs2},
+      disasm: "sw", "%c3rs2, %I_cl_uimm5x4(%c3rs1)",
+      semfunc: "&RV32::RiscVISw";
+    cj{: I_cj_imm11, x0 : next_pc, x0},
+      resources: {next_pc,x0 : next_pc[0..], x0[0..]},
+      disasm: "j", "0x%(@+I_cj_imm11:08x)",
+      semfunc: "&RV32::RiscVIJal";
+    cjal{: I_cj_imm11, x0 : next_pc, x1},
+      resources: {next_pc,x0 : next_pc[0..], x1[0..]},
+      disasm: "jal", "0x%(@+I_cj_imm11:08x)",
+      semfunc: "&RV32::RiscVIJal";
+    cjr{: crs1, x0 : next_pc, x0},
+      resources: {next_pc,crs1, x0 : next_pc[0..], x0[0..]},
+      disasm: "jr", "%crs1",
+      semfunc: "&RV32::RiscVIJalr";
+    cjalr{: crs1, x0 : next_pc, x1},
+      resources: {next_pc,crs1, x0 : next_pc[0..], x1[0..]},
+      disasm: "jalr", "%crs1",
+      semfunc: "&RV32::RiscVIJalr";
+    cbeqz{: c3rs1, x0, I_cb_imm8 : next_pc},
+      resources: {next_pc,c3rs1, x0 : next_pc[0..]},
+      disasm: "beqz", "%c3rs1, 0x%(@+I_cb_imm8:08x)",
+      semfunc: "&RV32::RiscVIBeq";
+    cbnez{: c3rs1, x0, I_cb_imm8 : next_pc},
+      resources: {next_pc,c3rs1, x0 : next_pc[0..]},
+      disasm: "bnez", "%c3rs1, 0x%(@+I_cb_imm8:08x)",
+      semfunc: "&RV32::RiscVIBne";
+    cli{: x0, I_ci_imm6 : rd},
+      resources: {next_pc,x0 : rd[0..]},
+      disasm: "li", "%rd, %I_ci_imm6",
+      semfunc: "&RV32::RiscVIAdd";
+    clui{: I_ci_imm6_12 : rd},
+      resources: {next_pc : rd[0..]},
+      disasm: "lui", "%rd, 0x%(I_ci_imm6_12:x)",
+      semfunc: "&RV32::RiscVILui";
+    caddi{: rd, I_ci_imm6 : rd},
+      resources: {next_pc, rd : rd[0..]},
+      disasm: "addi", "%rd, %rd, %I_ci_imm6",
+      semfunc: "&RV32::RiscVIAdd";
+    caddi16sp{: x2, I_ci_imm6x16 : x2},
+      resources: {next_pc, x2 : x2[0..]},
+      disasm: "addi", "%x2, %x2, %(I_ci_imm6x16:d)",
+      semfunc: "&RV32::RiscVIAdd";
+    caddi4spn{: x2, I_ciw_uimm8x4 : c3rd},
+      resources: {next_pc, x2 : c3rd[0..]},
+      disasm: "addi", "%c3rd, %x2, %I_ciw_uimm8x4",
+      semfunc: "&RV32::RiscVIAdd";
+    cslli{: rd, I_ci_uimm6 : rd},
+      resources: {next_pc, rd : rd[0..]},
+      disasm: "slli", "%rd, %rd, 0x%(I_ci_uimm6:x)",
+      semfunc: "&RV32::RiscVISll";
+    csrli{: c3rs1, I_ci_uimm6 : c3rs1},
+      resources: {next_pc, c3rs1 : c3rs1[0..]},
+      disasm: "srli", "%c3rs1, %c3rs1, 0x%(I_ci_uimm6:x)",
+      semfunc: "&RV32::RiscVISrl";
+    csrai{: c3rs1, I_ci_uimm6 : c3rs1},
+      resources: {next_pc, c3rs1 : c3rs1[0..]},
+      disasm: "srai", "%c3rs1, %c3rs1, 0x%(I_ci_uimm6:x)",
+      semfunc: "&RV32::RiscVISra";
+    candi{: c3rs1, I_ci_imm6 : c3rs1},
+      resources: {next_pc, c3rs1 : c3rs1[0..]},
+      disasm: "andi", "%c3rs1, %c3rs1, %I_ci_imm6",
+      semfunc: "&RV32::RiscVIAnd";
+    cmv{: crs2 , x0: crd},
+      resources: {next_pc, crs2, x0 : crd[0..]},
+      disasm: "mv", "%crd, %crs2",
+      semfunc: "&RV32::RiscVIAdd";
+    cadd{: crs2, crd: crd},
+      resources: {next_pc, crs2, crd : crd[0..]},
+      disasm: "add", "%crd, %crd, %crs2",
+      semfunc: "&RV32::RiscVIAdd";
+    cand{: c3rs1, c3rs2 : c3rs1},
+      resources: {next_pc, c3rs1, c3rs2 : c3rs1[0..]},
+      disasm: "and", "%c3rs1, %c3rs1, %c3rs2",
+      semfunc: "&RV32::RiscVIAnd";
+    cor{: c3rs1, c3rs2 : c3rs1},
+      resources: {next_pc, c3rs1, c3rs2 : c3rs1[0..]},
+      disasm: "or", "%c3rs1, %c3rs1, %c3rs2",
+      semfunc: "&RV32::RiscVIOr";
+    cxor{: c3rs1, c3rs2 : c3rs1},
+      resources: {next_pc, c3rs1, c3rs2 : c3rs1[0..]},
+      disasm: "xor", "%c3rs1, %c3rs1, %c3rs2",
+      semfunc: "&RV32::RiscVIXor";
+    csub{: c3rs1, c3rs2 : c3rs1},
+      resources: {next_pc, c3rs1, c3rs2 : c3rs1[0..]},
+      disasm: "sub", "%c3rs1, %c3rs1, %c3rs2",
+      semfunc: "&RV32::RiscVISub";
+    cnop{},
+      disasm: "nop",
+      resources: {next_pc},
+      semfunc: "&RiscVINop";
+    cebreak{},
+      disasm: "ebreak",
+      resources: {next_pc},
+      semfunc: "&RiscVIEbreak";
+  }
+}
+
+slot riscv_zca64 : riscv_zca32 {
+  includes {
+    #include "riscv/riscv_i_instructions.h"
+  }
+  default size = 2;
+  opcodes {
+    clwsp = override, semfunc: "&RV64::RiscVILw", "&RV64::RiscVILwChild";
+    cswsp = override, semfunc: "&RV64::RiscVISw";
+    clw = override, semfunc: "&RV64::RiscVILw", "&RV64::RiscVILwChild";
+    csw = override, semfunc: "&RV64::RiscVISw";
+    cj = override, semfunc: "&RV64::RiscVIJal";
+    cjal = override, semfunc: "&RV64::RiscVIJal";
+    cjr = override, semfunc: "&RV64::RiscVIJalr";
+    cjalr = override, semfunc: "&RV64::RiscVIJalr";
+    cbeqz = override, semfunc: "&RV64::RiscVIBeq";
+    cbnez = override, semfunc: "&RV64::RiscVIBne";
+    cli = override, semfunc: "&RV64::RiscVIAdd";
+    clui = override, semfunc: "&RV64::RiscVILui";
+    caddi = override, semfunc: "&RV64::RiscVIAdd";
+    caddi16sp = override, semfunc: "&RV64::RiscVIAdd";
+    caddi4spn = override, semfunc: "&RV64::RiscVIAdd";
+    cslli = override, semfunc: "&RV64::RiscVISll";
+    csrli = override, semfunc: "&RV64::RiscVISrl";
+    csrai = override, semfunc: "&RV64::RiscVISra";
+    candi = override, semfunc: "&RV64::RiscVIAnd";
+    cmv = override, semfunc: "&RV64::RiscVIAdd";
+    cadd = override, semfunc: "&RV64::RiscVIAdd";
+    cand = override, semfunc: "&RV64::RiscVIAnd";
+    cor = override, semfunc: "&RV64::RiscVIOr";
+    cxor = override, semfunc: "&RV64::RiscVIXor";
+    csub = override, semfunc: "&RV64::RiscVISub";
+  }
+}
+
+slot riscv_zcf32 {
+  includes {
+    #include "riscv/riscv_i_instructions.h"
+  }
+  default size = 2;
+  opcodes {
+    cflwsp{(: x2, I_ci_uimm6x4 : ), (: : c3frd)},
+      resources:{next_pc,x2 : cfrd[0..]},
+      disasm: "flw", "%frd, %I_ci_uimm6x4(%x2)",
+      semfunc: "&RV32::RiscVILw", "&RV32::RiscVILwChild";
+    cfswsp{: x2, I_css_uimm6x4, cfrs2 : },
+      resources: {next_pc,x2, cfrs2},
+      disasm: "fsw", "%cfrs2, %I_css_uimm6x4(%x2)",
+      semfunc: "&RV32::RiscVISw";
+    cflw{(: c3rs1, I_cl_uimm5x4 : ), (: : c3frd)},
+      resources: {next_pc,c3rs1 : c3frd[0..]},
+      disasm: "flw", "%c3frd, %I_cl_uimm5x4(%c3rs1)",
+      semfunc: "&RV32::RiscVILw", "&RV32::RiscVILwChild";
+    cfsw{: c3rs1, I_cl_uimm5x4, c3frs2 : },
+      resources: {next_pc,c3rs1, c3frs2},
+      disasm: "fsw", "%c3frs2, %I_cl_uimm5x4(%c3rs1)",
+      semfunc: "&RV32::RiscVISw";
+  }
+}
+
+slot riscv_zcf64 {
+  includes {
+    #include "riscv/riscv_i_instructions.h"
+  }
+  default size = 2;
+  opcodes {
+    cflwsp = override, semfunc: "&RV64::RiscVILw", "&RV64::RiscVILwChild";
+    cfswsp = override, semfunc: "&RV64::RiscVISw";
+    cflw = override, semfunc: "&RV64::RiscVILw", "&RV64::RiscVILwChild";
+    cfsw = override, semfunc: "&RV64::RiscVISw";
+  }
+}
+
+slot riscv_zcd32 {
+  includes {
+    #include "riscv/riscv_i_instructions.h"
+  }
+  default size = 2;
+  opcodes {
+    cfldsp{(: x2, I_ci_uimm6x8 : ), (: : c3drd)},
+      resources:{next_pc,x2 : drd[0..]},
+      disasm: "fld", "%drd, %I_ci_uimm6x8(%x2)",
+      semfunc: "&RV64::RiscVILd", "&RV64::RiscVILdChild";
+    cfsdsp{: x2, I_css_uimm6x8, cdrs2 : },
+      resources: {next_pc,x2, cdrs2},
+      disasm: "fsd", "%cdrs2, %I_css_uimm6x8(%x2)",
+      semfunc: "&RV64::RiscVISd";
+    cfld{(: c3rs1, I_cl_uimm5x8 : ), (: : c3drd)},
+      resources: {next_pc,c3rs1 : c3drd[0..]},
+      disasm: "fld", "%c3drd, %I_cl_uimm5x8(%c3rs1)",
+      semfunc: "&RV64::RiscVILd", "&RV64::RiscVILdChild";
+    cfsd{: c3rs1, I_cl_uimm5x8, c3drs2 : },
+      resources: {next_pc,c3rs1, c3drs2},
+      disasm: "fsd", "%c3drs2, %I_cl_uimm5x8(%c3rs1)",
+      semfunc: "&RV32::RiscVDSd";
+  }
+}
+
+slot riscv_zcd64 {
+  includes {
+    #include "riscv/riscv_i_instructions.h"
+  }
+  default size = 2;
+  opcodes {
+    cfldsp = override, semfunc: "&RV64::RiscVILd", "&RV64::RiscVILdChild";
+    cfsdsp = override, semfunc: "&RV64::RiscVISd";
+    cfld = override, semfunc: "&RV64::RiscVILd", "&RV64::RiscVILdChild";
+    cfsd = override, semfunc: "&RV64::RiscVDSd";
+  }
+}
+
+slot riscv_zcb32 {
+  includes {
+    #include "riscv/riscv_bitmanip_instructions.h"
+  }
+  default size = 2;
+    opcodes {
+    c_lbu{(: c3rs1, uimm2b),(: : c3rs2)},
+      resources: {c3rs1 : c3rs2},
+      disasm: "c.lbu", "%c3rs2, %uimm2b(%c3rs1)",
+      semfunc: "&RV32::RiscVILbu", "&RV32::RiscVILbuChild";
+    c_lhu{(: c3rs1, uimm2h),(: : c3rs2)},
+      resources: {c3rs1 : c3rs2},
+      disasm: "c.lhu", "c3rs2, %uimm2h(%c3rs1)",
+      semfunc: "&RV32::RiscVILhu", "&RV32::RiscVILhuChild";
+    c_lh{(: c3rs1, uimm2h),(: : c3rs2)},
+      resources: {c3rs1 : c3rs2},
+      disasm: "c.lh", "%c3rs2, %uimm2h(%c3rs1)",
+      semfunc: "&RV32::RiscVILh", "&RV32::RiscVILhChild";
+    c_sb{ : c3rs1, uimm2b, c3rs2},
+      resources: {c3rs1, c3rs2},
+      disasm: "c.sb", "%c3rs2, %uimm2b(%c3rs1)",
+      semfunc: "&RV32::RiscVISb";
+    c_sh{: c3rs1, uimm2h, c3rs2},
+      resources: {c3rs1, c3rs2},
+      disasm: "c.sh", "%c3rs2, %uimm2h(%c3rs1)",
+      semfunc: "&RV32::RiscVISh";
+    c_zext_b{: c3rs1 : c3rd},
+      resources: {c3rs1: c3rd},
+      disasm: "c.zext.b", "%c3rs1",
+      semfunc: "&RV32::RiscVZextB";
+    c_sext_b{: c3rs1 : c3rd},
+      resources: {c3rs1 : c3rd},
+      disasm: "c.sext.b", "c3rs1",
+      semfunc: "&RV32::RiscVSextB";
+    c_zext_h{: c3rs1 : c3rd},
+      resources: {c3rs1 : c3rd},
+      disasm: "c.zext.h", "c3rs1",
+      semfunc: "&RV32::RiscVZextH";
+    c_sext_h{: c3rs1 : c3rd},
+      resources: {c3rs1 : c3rd},
+      disasm: "c.sext.h", "c3rs1",
+      semfunc: "&RV32::RiscVSextH";
+    c_not{: c3rs1 : c3rd},
+      resources: {c3rs1 : c3rd},
+      disasm: "c.not", "c3rs1",
+      semfunc: "&RV32::RiscVNot";
+    c_mul{: c3rs1, c3rs2 : c3rd},
+      resources: {c3rs1, c3rs2 : c3rd},
+      disasm: "c.mul", "%c3rd, %c3rs2",
+      semfunc: "&RV32::MMul";
+  }
+}
+
+slot riscv_zcb64 {
+  includes {
+    #include "riscv/riscv_bitmanip_instructions.h"
+  }
+  default size = 2;
+    opcodes {
+    c_lbu = override, semfunc: "&RV64::RiscVILbu", "&RV64::RiscVILbuChild";
+    c_lhu = override, semfunc: "&RV64::RiscVLhu", "&RV64::RiscVILhuChild";
+    c_lh = override, semfunc: "&RV64::RiscVLh", "&RV64::RiscVILhChild";
+    c_sb = override, semfunc: "&RV64::RiscVSb";
+    c_sh = override, semfunc: "&RV64::RiscVSh";
+    c_zext_b = override, semfunc: "&RV64::RiscVZextB";
+    c_sext_b = override, semfunc: "&RV64::RiscVSextB";
+    c_zext_h = override, semfunc: "&RV64::RiscVZextH";
+    c_sext_h = override, semfunc: "&RV64::RiscVSextH";
+    c_not = override, semfunc: "&RV64::RiscV";
+    c_mul = override, semfunc: "&RV64::MMul";
+    c_sext_w{: c3rs1 : c3rd},
+      resources: {c3rs1 : c3rd},
+      disasm: "c.sext.w", "%c3rs1",
+      semfunc: "&RV64::RiscVSextW";
+  }
+}
+
+slot riscv_zcmp32 {
+  includes {
+    #include "riscv/riscv_zc_instructions.h"
+  }
+  default size = 2;
+  opcodes {
+    cm_push{: x2, spimm6, rlist, [rlist] : x2},
+      resources: {x2, [rlist] : x2},
+      disasm: "cm.push",
+      semfunc: "&RV32::RiscVZCmpPush";
+    cm_pop{: x2, spimm6, rlist : x2, [rlist]},
+      resources: {x2 : x2, [rlist]},
+      disasm: "cm.pop",
+      semfunc: "&RV32::RiscVZCmpPop";
+    cm_popret{: x2, spimm6, rlist : x2, [rlist]},
+      resources: {x2 : x2, [rlist]},
+      disasm: "cm.popret",
+      semfunc: "&RV32::RiscVZCmpPopRet";
+    cm_popretz{: x2, spimm6, rlist : x2, [rlist]},
+      resources: {x2 : x2, [rlist]},
+      disasm: "cm.popretz",
+      semfunc: "&RV32::RiscVZCmpPopRetz";
+    cm_mvsa01{ : x10, x11 : sreg1, sreg2},
+      resources: {x10, x11 : sreg1, sreg2},
+      disasm: "cm.mvsa01","%sreg1, %sreg2",
+      semfunc: "&RV32::RiscVZCmpMvTwoRegs";
+    cm_mva01s{ : sreg1, sreg2 : x10, x11 },
+      resources: {sreg1, sreg2 : x10, x11},
+      disasm: "cm.mva01s", "%sreg1, %sreg2",
+      semfunc: "&RV32::RiscVZCmpMvTwoRegs";
+  }
+}
+
+slot riscv_zcmp64 {
+  includes {
+    #include "riscv/riscv_zc_instructions.h"
+  }
+  default size = 2;
+  opcodes {
+    cm_push{ : x2, spimm6, rlist, [rlist] : x2},
+      resources: {[rlist]},
+      disasm: "cm.push",
+      semfunc: "&RV64::RiscVZCmpPush";
+    cm_pop{ : x2, spimm6, rlist : [rlist], x2},
+      resources: { : [rlist]},
+      disasm: "cm.pop",
+      semfunc: "&RV64::RiscVZCmpPop";
+    cm_popret{ : x2, spimm6, rlist, x1 : [rlist], x2, next_pc},
+      resources: { : [rlist]},
+      disasm: "cm.popret",
+      semfunc: "&RV64::RiscVZCmpPopRet";
+    cm_popretz{ : x2, spimm6, rlist, x1 : [rlist], x2, x10, next_pc},
+      resources: { : [rlist]},
+      disasm: "cm.popretz",
+      semfunc: "&RV64::RiscVZCmpPopRetz";
+    cm_mvsa01{ : x10, x11 : sreg1, sreg2},
+      resources: {x10, x11 : sreg1, sreg2},
+      disasm: "cm.mvsa01", "%sreg1, %sreg2",
+      semfunc: "&RV64::RiscVZCmpMvTwoRegs";
+    cm_mva01s{ : sreg1, sreg2 : x10, x11},
+      resources: {sreg1, sreg2 : x10, x11},
+      disasm: "cm.mva01s", "%sreg1, %sreg2",
+      semfunc: "&RV64::RiscVZCmpMvTwoRegs";
+  }
+}
+
+slot riscv_zcmt32 {
+  includes {
+    #include "riscv/riscv_zc_instructions.h"
+  }
+  default size = 2;
+  opcodes {
+    cm_jt{: index : next_pc},
+      resources: {: next_pc},
+      disasm: "cm.jt",
+      semfunc: "&RV32::RiscVZCmtJt";
+    cm_jalt{: index : next_pc, x1},
+      resources: {: next_pc, x1},
+      disasm: "cm.jalt",
+      semfunc: "&RV32::RiscVZCmtJalt";
+  }
+}
+
+slot riscv_zcmt64 {
+  includes {
+    #include "riscv/riscv_zc_instructions.h"
+  }
+  default size = 2;
+  opcodes {
+    cm_jt = override, semfunc: "&RV64::RiscVZCmtJt";
+    cm_jalt = override, semfunc: "&RV64::RiscVZCmtJalt";
+  }
+}
diff --git a/riscv/riscv_zc_getters.h b/riscv/riscv_zc_getters.h
new file mode 100644
index 0000000..8525582
--- /dev/null
+++ b/riscv/riscv_zc_getters.h
@@ -0,0 +1,472 @@
+// Copyright 2024 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.
+
+// This file adds operand getters for the Zc* extensions.
+
+#ifndef THIRD_PARTY_MPACT_RISCV_RISCV_ZC_GETTERS_H_
+#define THIRD_PARTY_MPACT_RISCV_RISCV_ZC_GETTERS_H_
+
+#include <cstdint>
+#include <vector>
+
+#include "absl/strings/str_cat.h"
+#include "mpact/sim/generic/immediate_operand.h"
+#include "mpact/sim/generic/literal_operand.h"
+#include "mpact/sim/generic/operand_interface.h"
+#include "mpact/sim/generic/resource_operand_interface.h"
+#include "riscv/riscv_encoding_common.h"
+#include "riscv/riscv_getter_helpers.h"
+#include "riscv/riscv_register_aliases.h"
+#include "riscv/riscv_state.h"
+
+namespace mpact::sim::riscv {
+using ::mpact::sim::generic::DestinationOperandInterface;
+using ::mpact::sim::generic::ImmediateOperand;
+using ::mpact::sim::generic::IntLiteralOperand;
+using ::mpact::sim::generic::ResourceOperandInterface;
+using ::mpact::sim::generic::SourceOperandInterface;
+
+using SourceOpIf = SourceOperandInterface;
+
+// The following function adds source operand getters to the given getter map.
+// The function uses the template parameters to get the correct enum type
+// for the instruction set being decoded. The Extractors parameter is used to
+// get the correct instruction format extractor for the instruction set. The
+// IntRegister and FpRegister parameters are used to get the correct register
+// types for the instruction set.
+
+// Getters for Zca source operands.
+
+template <typename Enum, typename Extractors, typename IntRegister,
+          typename FpRegister>
+void AddRiscVZcaSourceGetters(SourceOpGetterMap &getter_map,
+                              RiscVEncodingCommon *common) {
+  Insert(getter_map, *Enum::kC3rs1, [common]() {
+    auto num = Extractors::CS::ExtractCsRs1(common->inst_word());
+    return GetRegisterSourceOp<IntRegister>(
+        common->state(), absl::StrCat(RiscVState::kXregPrefix, num),
+        kXRegisterAliases[num]);
+  });
+  Insert(getter_map, *Enum::kC3rs2, [common]() {
+    auto num = Extractors::CS::ExtractCsRs2(common->inst_word());
+    return GetRegisterSourceOp<IntRegister>(
+        common->state(), absl::StrCat(RiscVState::kXregPrefix, num),
+        kXRegisterAliases[num]);
+  });
+  Insert(getter_map, *Enum::kCrd, [common]() {
+    auto num = Extractors::CR::ExtractRd(common->inst_word());
+    return GetRegisterSourceOp<IntRegister>(
+        common->state(), absl::StrCat(RiscVState::kXregPrefix, num),
+        kXRegisterAliases[num]);
+  });
+  Insert(getter_map, *Enum::kCrs1, [common]() {
+    auto num = Extractors::CR::ExtractRs1(common->inst_word());
+    return GetRegisterSourceOp<IntRegister>(
+        common->state(), absl::StrCat(RiscVState::kXregPrefix, num),
+        kXRegisterAliases[num]);
+  });
+  Insert(getter_map, *Enum::kCrs2, [common]() {
+    auto num = Extractors::CR::ExtractRs2(common->inst_word());
+    return GetRegisterSourceOp<IntRegister>(
+        common->state(), absl::StrCat(RiscVState::kXregPrefix, num),
+        kXRegisterAliases[num]);
+  });
+  Insert(getter_map, *Enum::kICbImm8, [common]() {
+    return new ImmediateOperand<int32_t>(
+        Extractors::Inst16Format::ExtractBimm(common->inst_word()));
+  });
+  Insert(getter_map, *Enum::kICiImm6, [common]() {
+    return new ImmediateOperand<int32_t>(
+        Extractors::CI::ExtractImm6(common->inst_word()));
+  });
+  Insert(getter_map, *Enum::kICiImm6x16, [common]() {
+    return new generic::ImmediateOperand<int32_t>(
+        Extractors::Inst16Format::ExtractCiImm10(common->inst_word()));
+  });
+  Insert(getter_map, *Enum::kICiUimm6, [common]() {
+    return new ImmediateOperand<uint32_t>(
+        Extractors::Inst16Format::ExtractUimm6(common->inst_word()));
+  });
+  Insert(getter_map, *Enum::kICiUimm6x4,
+         [common]() -> SourceOperandInterface * {
+           return new ImmediateOperand<uint32_t>(
+               Extractors::Inst16Format::ExtractCiImmW(common->inst_word()));
+         });
+  Insert(getter_map, *Enum::kICiwUimm8x4, [common]() {
+    return new ImmediateOperand<uint32_t>(
+        Extractors::Inst16Format::ExtractCiwImm10(common->inst_word()));
+  });
+  Insert(getter_map, *Enum::kICjImm11, [common]() {
+    return new ImmediateOperand<int32_t>(
+        Extractors::Inst16Format::ExtractJimm(common->inst_word()));
+  });
+  Insert(getter_map, *Enum::kIClUimm5x4, [common]() {
+    return new ImmediateOperand<uint32_t>(
+        Extractors::Inst16Format::ExtractClImmW(common->inst_word()));
+  });
+  Insert(getter_map, *Enum::kICssUimm6x4,
+         [common]() -> SourceOperandInterface * {
+           return new ImmediateOperand<uint32_t>(
+               Extractors::Inst16Format::ExtractCssImmW(common->inst_word()));
+         });
+  Insert(getter_map, *Enum::kX0,
+         []() { return new generic::IntLiteralOperand<0>({1}); });
+  Insert(getter_map, *Enum::kX2, [common]() {
+    return GetRegisterSourceOp<IntRegister>(
+        common->state(), absl::StrCat(RiscVState::kXregPrefix, 2),
+        kXRegisterAliases[2]);
+  });
+}
+
+// Getters for Zca destination operands.
+
+template <typename Enum, typename Extractors, typename IntRegister,
+          typename FpRegister>
+void AddRiscVZcaDestGetters(SourceOpGetterMap &getter_map,
+                            RiscVEncodingCommon *common) {
+  Insert(getter_map, *Enum::kC3rd, [common](int latency) {
+    int num = Extractors::CL::ExtractClRd(common->inst_word());
+    return GetRegisterDestinationOp<IntRegister>(
+        common->state(), absl::StrCat(RiscVState::kXregPrefix, num), latency);
+  });
+}
+
+// Getters for Zcb source operands (not covered in Zca).
+
+template <typename Enum, typename Extractors, typename IntRegister,
+          typename FpRegister>
+void AddRiscVZcbSourceGetters(SourceOpGetterMap &getter_map,
+                              RiscVEncodingCommon *common) {
+  Insert(getter_map, *Enum::kUimm2b, [common]() {
+    auto num = Extractors::CLB::ExtractUimm2(common->inst_word());
+    return new ImmediateOperand<uint32_t>(num);
+  });
+  Insert(getter_map, *Enum::kUimm2h, [common](int latency) {
+    auto num = Extractors::CLH::ExtractUimm2(common->inst_word());
+    return new ImmediateOperand<uint32_t>(num);
+  });
+}
+
+// Getters for Zcb destination operands (not covered in Zca).
+
+template <typename Enum, typename Extractors, typename IntRegister,
+          typename FpRegister>
+void AddRiscVZcbDestGetters(DestOpGetterMap &getter_map,
+                            RiscVEncodingCommon *common) {
+  Insert(getter_map, *Enum::kC3rs2, [common](int latency) {
+    int num = Extractors::CLB::ExtractClRd(common->inst_word());
+    return GetRegisterDestinationOp<IntRegister>(
+        common->state(), absl::StrCat(RiscVState::kXregPrefix, num), latency);
+  });
+}
+
+// Getters for Zcf source operands.
+
+template <typename Enum, typename Extractors, typename IntRegister,
+          typename FpRegister>
+void AddRiscVZcfSourceGetters(SourceOpGetterMap &getter_map,
+                              RiscVEncodingCommon *common) {
+  Insert(getter_map, *Enum::kC3frs2, [common]() {
+    auto num = Extractors::CS::ExtractCsRs2(common->inst_word());
+    return GetRegisterSourceOp<FpRegister>(
+        common->state(), absl::StrCat(RiscVState::kFregPrefix, num),
+        kFRegisterAliases[num]);
+  });
+  Insert(getter_map, *Enum::kC3rs1, [common]() {
+    auto num = Extractors::CS::ExtractCsRs1(common->inst_word());
+    return GetRegisterSourceOp<IntRegister>(
+        common->state(), absl::StrCat(RiscVState::kXregPrefix, num),
+        kXRegisterAliases[num]);
+  });
+  Insert(getter_map, *Enum::kCfrs2, [common]() {
+    auto num = Extractors::CR::ExtractRs2(common->inst_word());
+    return GetRegisterSourceOp<FpRegister>(
+        common->state(), absl::StrCat(RiscVState::kFregPrefix, num),
+        kFRegisterAliases[num]);
+  });
+  Insert(getter_map, *Enum::kICiUimm6x4,
+         [common]() -> SourceOperandInterface * {
+           return new ImmediateOperand<uint32_t>(
+               Extractors::Inst16Format::ExtractCiImmW(common->inst_word()));
+         });
+  Insert(getter_map, *Enum::kIClUimm5x4, [common]() {
+    return new ImmediateOperand<uint32_t>(
+        Extractors::Inst16Format::ExtractClImmW(common->inst_word()));
+  });
+  Insert(getter_map, *Enum::kICssUimm6x4, [common]() {
+    return new ImmediateOperand<uint32_t>(
+        Extractors::Inst16Format::ExtractCssImmW(common->inst_word()));
+  });
+  Insert(getter_map, *Enum::kX2, [common]() {
+    return GetRegisterSourceOp<IntRegister>(
+        common->state(), absl::StrCat(RiscVState::kXregPrefix, 2),
+        kXRegisterAliases[2]);
+  });
+}
+
+// Getters for Zcf destination operands.
+
+template <typename Enum, typename Extractors, typename IntRegister,
+          typename FpRegister>
+void AddRiscVZcfDestGetters(DestOpGetterMap &getter_map,
+                            RiscVEncodingCommon *common) {
+  Insert(getter_map, *Enum::kC3frd, [common](int latency) {
+    int num = Extractors::CL::ExtractClRd(common->inst_word());
+    return GetRegisterDestinationOp<FpRegister>(
+        common->state(), absl::StrCat(RiscVState::kFregPrefix, num), latency);
+  });
+}
+
+// Getters for Zcd source operands.
+
+template <typename Enum, typename Extractors, typename IntRegister,
+          typename FpRegister>
+void AddRiscVZcdSourceGetters(SourceOpGetterMap &getter_map,
+                              RiscVEncodingCommon *common) {
+  Insert(getter_map, *Enum::kC3drs2, [common]() {
+    auto num = Extractors::CS::ExtractCsRs2(common->inst_word());
+    return GetRegisterSourceOp<FpRegister>(
+        common->state(), absl::StrCat(RiscVState::kFregPrefix, num),
+        kFRegisterAliases[num]);
+  });
+  Insert(getter_map, *Enum::kC3rs1, [common]() {
+    auto num = Extractors::CS::ExtractCsRs1(common->inst_word());
+    return GetRegisterSourceOp<IntRegister>(
+        common->state(), absl::StrCat(RiscVState::kXregPrefix, num),
+        kXRegisterAliases[num]);
+  });
+  Insert(getter_map, *Enum::kCdrs2, [common]() {
+    auto num = Extractors::CR::ExtractRs2(common->inst_word());
+    return GetRegisterSourceOp<FpRegister>(
+        common->state(), absl::StrCat(RiscVState::kFregPrefix, num),
+        kFRegisterAliases[num]);
+  });
+  Insert(getter_map, *Enum::kICiUimm6x8,
+         [common]() -> SourceOperandInterface * {
+           return new ImmediateOperand<uint32_t>(
+               Extractors::Inst16Format::ExtractCiImmW(common->inst_word()));
+         });
+  Insert(getter_map, *Enum::kIClUimm5x8, [common]() {
+    return new ImmediateOperand<uint32_t>(
+        Extractors::Inst16Format::ExtractClImmW(common->inst_word()));
+  });
+  Insert(getter_map, *Enum::kICssUimm6x8, [common]() {
+    return new ImmediateOperand<uint32_t>(
+        Extractors::Inst16Format::ExtractCssImmW(common->inst_word()));
+  });
+  Insert(getter_map, *Enum::kX2, [common]() {
+    return GetRegisterSourceOp<IntRegister>(
+        common->state(), absl::StrCat(RiscVState::kXregPrefix, 2),
+        kXRegisterAliases[2]);
+  });
+}
+
+// Getters for Zcd destination operands.
+
+template <typename Enum, typename Extractors, typename IntRegister,
+          typename FpRegister>
+void AddRiscVZcdDestGetters(DestOpGetterMap &getter_map,
+                            RiscVEncodingCommon *common) {
+  Insert(getter_map, *Enum::kC3drd, [common](int latency) {
+    int num = Extractors::CL::ExtractClRd(common->inst_word());
+    return GetRegisterDestinationOp<IntRegister>(
+        common->state(), absl::StrCat(RiscVState::kFregPrefix, num), latency);
+  });
+}
+
+// The following table maps Zcmp Sreg alias register numbers to Xreg numbers.
+constexpr int kSRegToXRegMap[8] = {8, 9, 18, 19, 20, 21, 22, 23};
+
+// Getters for Zcmp source operands.
+
+template <typename Enum, typename Extractors, typename IntRegister,
+          typename FpRegister>
+void AddRiscVZcmpSourceGetters(SourceOpGetterMap &getter_map,
+                               RiscVEncodingCommon *common) {
+  Insert(getter_map, *Enum::kRlist, [common]() {
+    return new ImmediateOperand<uint32_t>(
+        Extractors::CMMP::ExtractRlist(common->inst_word()));
+  });
+  Insert(getter_map, *Enum::kSpimm, [common]() {
+    return new ImmediateOperand<uint32_t>(
+        Extractors::CMMP::ExtractSpimm(common->inst_word()));
+  });
+  Insert(getter_map, *Enum::kSreg1, [common]() {
+    int num =
+        kSRegToXRegMap[Extractors::CMMV::ExtractRs1p(common->inst_word())];
+    return GetRegisterSourceOp<IntRegister>(
+        common->state(), absl::StrCat(RiscVState::kXregPrefix, num),
+        kXRegisterAliases[num]);
+  });
+  Insert(getter_map, *Enum::kSreg2, [common]() {
+    int num =
+        kSRegToXRegMap[Extractors::CMMV::ExtractRs2p(common->inst_word())];
+    return GetRegisterSourceOp<IntRegister>(
+        common->state(), absl::StrCat(RiscVState::kXregPrefix, num),
+        kXRegisterAliases[num]);
+  });
+  Insert(getter_map, *Enum::kX2, [common]() {
+    return GetRegisterSourceOp<IntRegister>(
+        common->state(), absl::StrCat(RiscVState::kXregPrefix, 2),
+        kXRegisterAliases[2]);
+  });
+  Insert(getter_map, *Enum::kX10, [common]() {
+    return GetRegisterSourceOp<IntRegister>(
+        common->state(), absl::StrCat(RiscVState::kXregPrefix, 10),
+        kXRegisterAliases[10]);
+  });
+  Insert(getter_map, *Enum::kX11, [common]() {
+    return GetRegisterSourceOp<IntRegister>(
+        common->state(), absl::StrCat(RiscVState::kXregPrefix, 11),
+        kXRegisterAliases[11]);
+  });
+}
+
+// Getters for Zcmp destination operands.
+
+template <typename Enum, typename Extractors, typename IntRegister,
+          typename FpRegister>
+void AddRiscVZcmpDestGetters(DestOpGetterMap &getter_map,
+                             RiscVEncodingCommon *common) {
+  Insert(getter_map, *Enum::kSreg1, [common](int latency) {
+    int num =
+        kSRegToXRegMap[Extractors::CMMV::ExtractRs1p(common->inst_word())];
+    return GetRegisterDestinationOp<IntRegister>(
+        common->state(), absl::StrCat(RiscVState::kXregPrefix, num), latency);
+  });
+  Insert(getter_map, *Enum::kSreg2, [common](int latency) {
+    int num =
+        kSRegToXRegMap[Extractors::CMMV::ExtractRs2p(common->inst_word())];
+    return GetRegisterDestinationOp<IntRegister>(
+        common->state(), absl::StrCat(RiscVState::kXregPrefix, num), latency);
+  });
+  Insert(getter_map, *Enum::kX2, [common](int latency) {
+    return GetRegisterDestinationOp<IntRegister>(
+        common->state(), absl::StrCat(RiscVState::kXregPrefix, 2), latency);
+  });
+  Insert(getter_map, *Enum::kX10, [common](int latency) {
+    return GetRegisterDestinationOp<IntRegister>(
+        common->state(), absl::StrCat(RiscVState::kXregPrefix, 10), latency);
+  });
+  Insert(getter_map, *Enum::kX11, [common](int latency) {
+    return GetRegisterDestinationOp<IntRegister>(
+        common->state(), absl::StrCat(RiscVState::kXregPrefix, 11), latency);
+  });
+}
+
+// Getters for Zcmp list source operands.
+
+template <typename Enum, typename Extractors, typename IntRegister,
+          typename FpRegister>
+void AddRiscVZcmpListSourceGetters(ListSourceOpGetterMap &getter_map,
+                                   RiscVEncodingCommon *common) {
+  Insert(getter_map, *Enum::kRlist, [common]() {
+    std::vector<SourceOperandInterface *> result;
+    int rlist = Extractors::CMMP::ExtractRlist(common->inst_word());
+    // Get the value of 'rlist', and add source operands accordingly.
+    if (rlist < 4) return result;
+    result.push_back(GetRegisterSourceOp<IntRegister>(common->state(), "x1"));
+    if (rlist == 4) return result;
+    result.push_back(GetRegisterSourceOp<IntRegister>(common->state(), "x8"));
+    if (rlist == 5) return result;
+    result.push_back(GetRegisterSourceOp<IntRegister>(common->state(), "x9"));
+    if (rlist == 6) return result;
+    result.push_back(GetRegisterSourceOp<IntRegister>(common->state(), "x18"));
+    if (rlist == 7) return result;
+    result.push_back(GetRegisterSourceOp<IntRegister>(common->state(), "x19"));
+    if (rlist == 8) return result;
+    result.push_back(GetRegisterSourceOp<IntRegister>(common->state(), "x20"));
+    if (rlist == 9) return result;
+    result.push_back(GetRegisterSourceOp<IntRegister>(common->state(), "x21"));
+    if (rlist == 10) return result;
+    result.push_back(GetRegisterSourceOp<IntRegister>(common->state(), "x22"));
+    if (rlist == 11) return result;
+    result.push_back(GetRegisterSourceOp<IntRegister>(common->state(), "x23"));
+    if (rlist == 12) return result;
+    result.push_back(GetRegisterSourceOp<IntRegister>(common->state(), "x24"));
+    if (rlist == 13) return result;
+    result.push_back(GetRegisterSourceOp<IntRegister>(common->state(), "x25"));
+    if (rlist == 14) return result;
+    result.push_back(GetRegisterSourceOp<IntRegister>(common->state(), "x26"));
+    result.push_back(GetRegisterSourceOp<IntRegister>(common->state(), "x27"));
+    return result;
+  });
+}
+
+// Getters for Zcmp list destination operands.
+
+template <typename Enum, typename Extractors, typename IntRegister,
+          typename FpRegister>
+void AddRiscVZcmpListDestGetters(ListDestOpGetterMap &getter_map,
+                                 RiscVEncodingCommon *common) {
+  Insert(getter_map, *Enum::kRlist, [common](std::vector<int> latency) {
+    std::vector<DestinationOperandInterface *> result;
+    int rlist = Extractors::CMMP::ExtractRlist(common->inst_word());
+    // Get the value of 'rlist', and add destination operands accordingly.
+    int size = latency.size();
+    if (rlist < 4) return result;
+    result.push_back(GetRegisterDestinationOp<IntRegister>(
+        common->state(), "x1", latency[result.size() % size]));
+    if (rlist == 4) return result;
+    result.push_back(GetRegisterDestinationOp<IntRegister>(
+        common->state(), "x8", latency[result.size() % size]));
+    if (rlist == 5) return result;
+    result.push_back(GetRegisterDestinationOp<IntRegister>(
+        common->state(), "x9", latency[result.size() % size]));
+    if (rlist == 6) return result;
+    result.push_back(GetRegisterDestinationOp<IntRegister>(
+        common->state(), "x18", latency[result.size() % size]));
+    if (rlist == 7) return result;
+    result.push_back(GetRegisterDestinationOp<IntRegister>(
+        common->state(), "x19", latency[result.size() % size]));
+    if (rlist == 8) return result;
+    result.push_back(GetRegisterDestinationOp<IntRegister>(
+        common->state(), "x20", latency[result.size() % size]));
+    if (rlist == 9) return result;
+    result.push_back(GetRegisterDestinationOp<IntRegister>(
+        common->state(), "x21", latency[result.size() % size]));
+    if (rlist == 10) return result;
+    result.push_back(GetRegisterDestinationOp<IntRegister>(
+        common->state(), "x22", latency[result.size() % size]));
+    if (rlist == 11) return result;
+    result.push_back(GetRegisterDestinationOp<IntRegister>(
+        common->state(), "x23", latency[result.size() % size]));
+    if (rlist == 12) return result;
+    result.push_back(GetRegisterDestinationOp<IntRegister>(
+        common->state(), "x24", latency[result.size() % size]));
+    if (rlist == 13) return result;
+    result.push_back(GetRegisterDestinationOp<IntRegister>(
+        common->state(), "x25", latency[result.size() % size]));
+    if (rlist == 14) return result;
+    result.push_back(GetRegisterDestinationOp<IntRegister>(
+        common->state(), "x26", latency[result.size() % size]));
+    result.push_back(GetRegisterDestinationOp<IntRegister>(
+        common->state(), "x27", latency[result.size() % size]));
+    return result;
+  });
+}
+
+template <typename Enum, typename Extractors, typename IntRegister,
+          typename FpRegister>
+void AddRiscVZcmtSourceGetters(SourceOpGetterMap &getter_map,
+                               RiscVEncodingCommon *common) {
+  Insert(getter_map, *Enum::kJtIndex, [common](int latency) {
+    int num = Extractors::CMJT::ExtractIndex(common->inst_word());
+    return new ImmediateOperand<uint32_t>(num);
+  });
+}
+
+}  // namespace mpact::sim::riscv
+
+#endif  // THIRD_PARTY_MPACT_RISCV_RISCV_ZC_GETTERS_H_
diff --git a/riscv/riscv_zc_instructions.cc b/riscv/riscv_zc_instructions.cc
new file mode 100644
index 0000000..f7643cb
--- /dev/null
+++ b/riscv/riscv_zc_instructions.cc
@@ -0,0 +1,303 @@
+// Copyright 2024 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 "riscv/riscv_zc_instructions.h"
+
+#include <cstdint>
+#include <type_traits>
+
+#include "mpact/sim/generic/instruction.h"
+#include "riscv/riscv_instruction_helpers.h"
+#include "riscv/riscv_register.h"
+#include "riscv/riscv_state.h"
+
+// This file implements the semantic functions for the Zcm* extensions.
+
+namespace mpact::sim::riscv {
+
+using ::mpact::sim::generic::Instruction;
+
+namespace RV32 {
+
+namespace {
+
+constexpr int kStackAdjBase[] = {
+    0, 0, 0, 0, 16, 16, 16, 16, 32, 32, 32, 32, 48, 48, 48, 64,
+};
+
+}  // namespace
+
+using RegType = ::mpact::sim::riscv::RV32Register;
+using UIntReg = typename std::make_unsigned<typename RegType::ValueType>::type;
+using IntReg = typename std::make_signed<UIntReg>::type;
+
+// Zcmp instructions.
+void RiscVZCmpPush(const Instruction *inst) {
+  RiscVState *state = static_cast<RiscVState *>(inst->state());
+  int num_regs = inst->SourcesSize() - 3;
+  auto *db = state->db_factory()->Allocate<UIntReg>(num_regs);
+  auto db_span = db->Get<UIntReg>();
+  // Get the register values and put them in the data buffer.
+  for (int i = 0; i < num_regs; ++i) {
+    auto value = generic::GetInstructionSource<UIntReg>(inst, i + 3);
+    db_span[i] = value;
+  }
+  // Store the data buffer to memory.
+  auto sp = generic::GetInstructionSource<UIntReg>(inst, 0);
+  state->StoreMemory(inst, sp - sizeof(UIntReg) * num_regs, db);
+  db->DecRef();
+  // Compute the stack adjustment.
+  auto spimm6 = generic::GetInstructionSource<UIntReg>(inst, 1);
+  auto rlist = generic::GetInstructionSource<UIntReg>(inst, 2);
+  auto sp_adjustment = spimm6 + kStackAdjBase[rlist];
+  // Compute the new stack pointer.
+  sp -= sp_adjustment;
+  RiscVWriteReg<RegType, UIntReg>(inst, 0, sp);
+}
+
+namespace {
+
+// This helper pops the number of registers specified into the appropriate
+// destination operands, and then adjusts the stack pointer.
+void RiscVZCmpPopHelper(const Instruction *inst, int size) {
+  RiscVState *state = static_cast<RiscVState *>(inst->state());
+  // Compute the stack adjustment.
+  auto spimm6 = generic::GetInstructionSource<UIntReg>(inst, 1);
+  auto rlist = generic::GetInstructionSource<UIntReg>(inst, 2);
+  auto sp_adjustment = spimm6 + kStackAdjBase[rlist];
+  auto *db = state->db_factory()->Allocate<UIntReg>(size);
+  // Load registers from the stack.
+  auto sp = generic::GetInstructionSource<UIntReg>(inst, 0);
+  // Start address = sp + sp_adjustment - sizeof(UIntReg) * size;
+  uint64_t start_address = sp + sp_adjustment - sizeof(UIntReg) * size;
+  state->LoadMemory(inst, start_address, db, nullptr, nullptr);
+  auto db_span = db->Get<UIntReg>();
+  for (int i = 0; i < size; ++i) {
+    RiscVWriteReg<RegType, UIntReg>(inst, i, db_span[i]);
+  }
+  // Write to the stack pointer register.
+  db->DecRef();
+  RiscVWriteReg<RegType, UIntReg>(inst, size, sp + sp_adjustment);
+}
+
+}  // namespace
+
+void RiscVZCmpPop(const Instruction *inst) {
+  // Size is the number of registers to pop.
+  int size = inst->DestinationsSize() - 1;
+  RiscVZCmpPopHelper(inst, size);
+}
+
+void RiscVZCmpPopRet(const Instruction *inst) {
+  // Size is the number of registers to pop.
+  int size = inst->DestinationsSize() - 2;  // x2 and next_pc.
+  RiscVZCmpPopHelper(inst, size);
+  // Now perform the return.
+  UIntReg target = generic::GetInstructionSource<UIntReg>(inst, 3);
+  auto *db = inst->Destination(size + 1)->AllocateDataBuffer();
+  db->SetSubmit<UIntReg>(0, target);
+}
+
+void RiscVZCmpPopRetz(const Instruction *inst) {
+  // Size is the number of registers to pop.
+  int size = inst->DestinationsSize() - 3;  // x2, x10, and next_pc.
+  RiscVZCmpPopHelper(inst, size);
+  // Now clear a0.
+  RiscVWriteReg<RegType, UIntReg>(inst, size + 1, 0);
+  // Now perform the return.
+  UIntReg target = generic::GetInstructionSource<UIntReg>(inst, 3);
+  auto *db = inst->Destination(size + 2)->AllocateDataBuffer();
+  db->SetSubmit<UIntReg>(0, target);
+}
+
+void RiscVZCmpMvTwoRegs(const Instruction *inst) {
+  RiscVWriteReg<RegType, UIntReg>(
+      inst, 0, generic::GetInstructionSource<UIntReg>(inst, 0));
+  RiscVWriteReg<RegType, UIntReg>(
+      inst, 1, generic::GetInstructionSource<UIntReg>(inst, 1));
+}
+
+// Zcmt instructions.
+namespace {
+
+void RiscVZCmtJtHelper(const Instruction *inst, int dest_index) {
+  int index = generic::GetInstructionSource<UIntReg>(inst, 0);
+  auto *state = static_cast<RiscVState *>(inst->state());
+  auto jvt_value = state->jvt()->AsUint64();
+  auto mode = jvt_value & 0x3f;
+  if (mode != 0) {
+    state->Trap(/*is_interrupt=*/false, 0, *ExceptionCode::kIllegalInstruction,
+                inst->address(), inst);
+    return;
+  }
+  // Load target address from the jump table.
+  UIntReg entry_address = (jvt_value & ~0x3f) + (index * sizeof(UIntReg));
+  auto *db = state->db_factory()->Allocate<UIntReg>(1);
+  state->LoadMemory(inst, entry_address, db, nullptr, nullptr);
+  UIntReg target_address = db->Get<UIntReg>(0);
+  db->DecRef();
+  // Write the target address to the next pc operand.
+  auto *target_db = inst->Destination(0)->AllocateDataBuffer();
+  target_db->SetSubmit<UIntReg>(0, target_address);
+}
+
+}  // namespace
+
+void RiscVZCmtJt(const Instruction *inst) { RiscVZCmtJtHelper(inst, 0); }
+
+void RiscVZCmtJalt(const Instruction *inst) {
+  RiscVZCmtJtHelper(inst, 1);
+  // Write the return address to the x1 (ra) operand.
+  RiscVWriteReg<RegType, UIntReg>(inst, 1, inst->address() + inst->size());
+}
+
+}  // namespace RV32
+
+namespace RV64 {
+
+namespace {
+
+constexpr int kStackAdjBase[] = {
+    0, 0, 0, 0, 16, 16, 32, 32, 48, 48, 64, 64, 80, 80, 96, 112,
+};
+
+}  // namespace
+
+using RegType = ::mpact::sim::riscv::RV64Register;
+using UIntReg = typename std::make_unsigned<typename RegType::ValueType>::type;
+using IntReg = typename std::make_signed<UIntReg>::type;
+
+// Zcmp instructions.
+void RiscVZCmpPush(const Instruction *inst) {
+  RiscVState *state = static_cast<RiscVState *>(inst->state());
+  int num_regs = inst->SourcesSize() - 3;
+  auto *db = state->db_factory()->Allocate<UIntReg>(num_regs);
+  auto db_span = db->Get<UIntReg>();
+  // Get the register values and put them in the data buffer.
+  for (int i = 0; i < num_regs; ++i) {
+    auto value = generic::GetInstructionSource<UIntReg>(inst, i + 3);
+    db_span[i] = value;
+  }
+  // Store the data buffer to memory.
+  auto sp = generic::GetInstructionSource<UIntReg>(inst, 0);
+  state->StoreMemory(inst, sp - sizeof(UIntReg) * num_regs, db);
+  db->DecRef();
+  // Compute the stack adjustment.
+  auto spimm6 = generic::GetInstructionSource<UIntReg>(inst, 1);
+  auto rlist = generic::GetInstructionSource<UIntReg>(inst, 2);
+  auto sp_adjustment = spimm6 + kStackAdjBase[rlist];
+  // Compute the new stack pointer.
+  sp -= sp_adjustment;
+  RiscVWriteReg<RegType, UIntReg>(inst, 0, sp);
+}
+
+namespace {
+
+// This helper pops the number of registers specified into the appropriate
+// destination operands, and then adjusts the stack pointer.
+void RiscVZCmpPopHelper(const Instruction *inst, int size) {
+  RiscVState *state = static_cast<RiscVState *>(inst->state());
+  // Compute the stack adjustment.
+  auto spimm6 = generic::GetInstructionSource<UIntReg>(inst, 1);
+  auto rlist = generic::GetInstructionSource<UIntReg>(inst, 2);
+  auto sp_adjustment = spimm6 + kStackAdjBase[rlist];
+  auto *db = state->db_factory()->Allocate<UIntReg>(size);
+  // Load registers from the stack.
+  auto sp = generic::GetInstructionSource<UIntReg>(inst, 0);
+  // Start address = sp + sp_adjustment - sizeof(UIntReg) * size;
+  uint64_t start_address = sp + sp_adjustment - sizeof(UIntReg) * size;
+  state->LoadMemory(inst, start_address, db, nullptr, nullptr);
+  auto db_span = db->Get<UIntReg>();
+  for (int i = 0; i < size; ++i) {
+    RiscVWriteReg<RegType, UIntReg>(inst, i, db_span[i]);
+  }
+  // Write to the stack pointer register.
+  db->DecRef();
+  RiscVWriteReg<RegType, UIntReg>(inst, size, sp + sp_adjustment);
+}
+
+}  // namespace
+
+void RiscVZCmpPop(const Instruction *inst) {
+  // Size is the number of registers to pop.
+  int size = inst->DestinationsSize() - 1;
+  RiscVZCmpPopHelper(inst, size);
+}
+
+void RiscVZCmpPopRet(const Instruction *inst) {
+  // Size is the number of registers to pop.
+  int size = inst->DestinationsSize() - 2;  // x2 and next_pc.
+  RiscVZCmpPopHelper(inst, size);
+  // Now perform the return.
+  UIntReg target = generic::GetInstructionSource<UIntReg>(inst, 3);
+  auto *db = inst->Destination(size + 1)->AllocateDataBuffer();
+  db->SetSubmit<UIntReg>(0, target);
+}
+
+void RiscVZCmpPopRetz(const Instruction *inst) {
+  // Size is the number of registers to pop.
+  int size = inst->DestinationsSize() - 3;  // x2, x10, and next_pc.
+  RiscVZCmpPopHelper(inst, size);
+  // Now clear a0.
+  RiscVWriteReg<RegType, UIntReg>(inst, size + 1, 0);
+  // Now perform the return.
+  UIntReg target = generic::GetInstructionSource<UIntReg>(inst, 3);
+  auto *db = inst->Destination(size + 2)->AllocateDataBuffer();
+  db->SetSubmit<UIntReg>(0, target);
+}
+
+void RiscVZCmpMvTwoRegs(const Instruction *inst) {
+  RiscVWriteReg<RegType, UIntReg>(
+      inst, 0, generic::GetInstructionSource<UIntReg>(inst, 0));
+  RiscVWriteReg<RegType, UIntReg>(
+      inst, 1, generic::GetInstructionSource<UIntReg>(inst, 1));
+}
+
+// Zcmt instructions.
+namespace {
+
+void RiscVZCmtJtHelper(const Instruction *inst, int dest_index) {
+  int index = generic::GetInstructionSource<UIntReg>(inst, 0);
+  auto *state = static_cast<RiscVState *>(inst->state());
+  auto jvt_value = state->jvt()->AsUint64();
+  auto mode = jvt_value & 0x3f;
+  if (mode != 0) {
+    state->Trap(/*is_interrupt=*/false, 0, *ExceptionCode::kIllegalInstruction,
+                inst->address(), inst);
+    return;
+  }
+  // Load target address from the jump table.
+  UIntReg entry_address = (jvt_value & ~0x3f) + (index * sizeof(UIntReg));
+  auto *db = state->db_factory()->Allocate<UIntReg>(1);
+  state->LoadMemory(inst, entry_address, db, nullptr, nullptr);
+  UIntReg target_address = db->Get<UIntReg>(0);
+  db->DecRef();
+  // Write the target address to the next pc operand.
+  auto *target_db = inst->Destination(0)->AllocateDataBuffer();
+  target_db->SetSubmit<UIntReg>(0, target_address);
+}
+
+}  // namespace
+
+void RiscVZCmtJt(const Instruction *inst) { RiscVZCmtJtHelper(inst, 0); }
+
+void RiscVZCmtJalt(const Instruction *inst) {
+  RiscVZCmtJtHelper(inst, 1);
+  // Write the return address to the x1 (ra) operand.
+  RiscVWriteReg<RegType, UIntReg>(inst, 1, inst->address() + inst->size());
+}
+
+}  // namespace RV64
+
+}  // namespace mpact::sim::riscv
diff --git a/riscv/riscv_zc_instructions.h b/riscv/riscv_zc_instructions.h
new file mode 100644
index 0000000..005a707
--- /dev/null
+++ b/riscv/riscv_zc_instructions.h
@@ -0,0 +1,58 @@
+// Copyright 2024 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.
+
+#ifndef THIRD_PARTY_MPACT_RISCV_RISCV_ZC_INSTRUCTIONS_H_
+#define THIRD_PARTY_MPACT_RISCV_RISCV_ZC_INSTRUCTIONS_H_
+
+#include "mpact/sim/generic/instruction.h"
+
+// This file declares the semantic functions specific to the Zcm* extensions.
+
+namespace mpact::sim::riscv {
+
+using ::mpact::sim::generic::Instruction;
+
+namespace RV32 {
+
+// Zcmp instructions.
+void RiscVZCmpPush(const Instruction *inst);
+void RiscVZCmpPop(const Instruction *inst);
+void RiscVZCmpPopRet(const Instruction *inst);
+void RiscVZCmpPopRetz(const Instruction *inst);
+void RiscVZCmpMvTwoRegs(const Instruction *inst);
+
+// Zcmt instructions.
+void RiscVZCmtJt(const Instruction *inst);
+void RiscVZCmtJalt(const Instruction *inst);
+
+}  // namespace RV32
+
+namespace RV64 {
+
+// Zcmp instructions.
+void RiscVZCmpPush(const Instruction *inst);
+void RiscVZCmpPop(const Instruction *inst);
+void RiscVZCmpPopRet(const Instruction *inst);
+void RiscVZCmpPopRetz(const Instruction *inst);
+void RiscVZCmpMvTwoRegs(const Instruction *inst);
+
+// Zcmt instructions.
+void RiscVZCmtJt(const Instruction *inst);
+void RiscVZCmtJalt(const Instruction *inst);
+
+}  // namespace RV64
+
+}  // namespace mpact::sim::riscv
+
+#endif  // THIRD_PARTY_MPACT_RISCV_RISCV_ZC_INSTRUCTIONS_H_
diff --git a/riscv/rvm23.bin_fmt b/riscv/rvm23.bin_fmt
index 7c13e74..fab85ed 100644
--- a/riscv/rvm23.bin_fmt
+++ b/riscv/rvm23.bin_fmt
@@ -34,9 +34,12 @@
     RiscVZihintntl,
   };
   RVM23Inst16 = {
-    RiscVCInst16,
     RiscVZcmop,
     RiscVCZihintntl,
+    RiscVCZca32,
+    RiscVCZcb32,
+    RiscVCZcmp32,
+    RiscVCZcmt32,
   };
 }
 
@@ -46,3 +49,4 @@
 #include "riscv/riscv_zimop.bin_fmt"
 #include "riscv/riscv_zhintpause.bin_fmt"
 #include "riscv/riscv_zihintntl.bin_fmt"
+#include "riscv/riscv_zc.bin_fmt"
diff --git a/riscv/rvm23.isa b/riscv/rvm23.isa
index 91852a1..c298097 100644
--- a/riscv/rvm23.isa
+++ b/riscv/rvm23.isa
@@ -22,6 +22,7 @@
 #include "riscv/riscv32g.isa"
 #include "riscv/riscv32zb.isa"
 #include "riscv/riscv_vector.isa"
+#include "riscv/riscv_zc.isa"
 #include "riscv/riscv_zicond.isa"
 #include "riscv/riscv_zimop.isa"
 #include "riscv/riscv_zhintpause.isa"
@@ -29,7 +30,6 @@
 
 slot rvm23 :
   riscv32i,
-  riscv32c,
   riscv32m,
   riscv32_amo_arithmetic,
   riscv32f,
@@ -44,7 +44,11 @@
   riscv_zcmop,
   riscv_zhintpause,
   riscv_zihintntl,
-  riscv_czihintntl {
+  riscv_czihintntl,
+  riscv_zca32,
+  riscv_zcb32,
+  riscv_zcmp32,
+  riscv_zcmt32 {
   default opcode =
     disasm: "Illegal instruction at 0x%(@:08x)",
     semfunc: "&RiscVIllegalInstruction";
diff --git a/riscv/test/BUILD b/riscv/test/BUILD
index 927fafb..2bc43a7 100644
--- a/riscv/test/BUILD
+++ b/riscv/test/BUILD
@@ -760,3 +760,33 @@
         "@com_google_mpact-sim//mpact/sim/generic:instruction",
     ],
 )
+
+cc_test(
+    name = "riscv_zc_instructions_test",
+    size = "small",
+    srcs = ["riscv_zc_instructions_test.cc"],
+    deps = [
+        "//riscv:riscv_state",
+        "//riscv:rvm23_instructions",
+        "@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/util/memory",
+    ],
+)
+
+cc_test(
+    name = "riscv_zc64_instructions_test",
+    size = "small",
+    srcs = ["riscv_zc64_instructions_test.cc"],
+    deps = [
+        "//riscv:riscv_state",
+        "//riscv:rvm23_instructions",
+        "@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/util/memory",
+    ],
+)
diff --git a/riscv/test/riscv_zc64_instructions_test.cc b/riscv/test/riscv_zc64_instructions_test.cc
new file mode 100644
index 0000000..2d97701
--- /dev/null
+++ b/riscv/test/riscv_zc64_instructions_test.cc
@@ -0,0 +1,502 @@
+// Copyright 2024 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 <cstring>
+#include <string>
+#include <tuple>
+#include <vector>
+
+#include "absl/strings/string_view.h"
+#include "googlemock/include/gmock/gmock.h"
+#include "mpact/sim/generic/immediate_operand.h"
+#include "mpact/sim/generic/instruction.h"
+#include "mpact/sim/util/memory/flat_demand_memory.h"
+#include "riscv/riscv_register.h"
+#include "riscv/riscv_state.h"
+#include "riscv/riscv_zc_instructions.h"
+
+// This file contains the unit tests for the 64 bit Zcmp* instructions that
+// are not covered elsewhere.
+
+namespace {
+
+using ::mpact::sim::generic::ImmediateOperand;
+using ::mpact::sim::generic::Instruction;
+using ::mpact::sim::riscv::RiscVState;
+using ::mpact::sim::riscv::RiscVXlen;
+using ::mpact::sim::riscv::RV64Register;
+using ::mpact::sim::util::FlatDemandMemory;
+
+constexpr uint64_t kInstAddress = 0x2468;
+constexpr char kX1[] = "x1";
+constexpr char kX2[] = "x2";
+constexpr char kX8[] = "x8";
+constexpr char kX9[] = "x9";
+constexpr char kX10[] = "x10";
+constexpr char kX11[] = "x11";
+constexpr char kX18[] = "x18";
+constexpr char kX19[] = "x19";
+constexpr char kX20[] = "x20";
+constexpr char kX21[] = "x21";
+constexpr char kX22[] = "x22";
+constexpr char kX23[] = "x23";
+constexpr char kX24[] = "x24";
+constexpr char kX25[] = "x25";
+constexpr char kX26[] = "x26";
+constexpr char kX27[] = "x27";
+constexpr char kX30[] = "x30";
+constexpr char kX31[] = "x31";
+constexpr uint64_t kMemAddress = 0x1000;
+
+// The test fixture allocates a machine state object and an instruction object.
+// It also contains convenience methods for interacting with the instruction
+// object in a more short hand form.
+class RV64ZcInstructionTest : public testing::Test {
+ public:
+  RV64ZcInstructionTest() {
+    memory_ = new FlatDemandMemory();
+    state_ = new RiscVState("test", RiscVXlen::RV64, memory_);
+    instruction_ = new Instruction(kInstAddress, state_);
+    instruction_->set_size(4);
+    // Set the jump table address to 0x4000.
+    state_->jvt()->Set(static_cast<uint64_t>(0x4000));
+    auto *db = state_->db_factory()->Allocate<uint64_t>(256);
+    auto db_span = db->Get<uint64_t>();
+    for (auto i = 0; i < 256; ++i) {
+      db_span[i] = 0x8000 + i * sizeof(uint64_t);
+    }
+    state_->StoreMemory(nullptr, 0x4000, db);
+    db->DecRef();
+  }
+
+  ~RV64ZcInstructionTest() override {
+    delete memory_;
+    delete state_;
+    instruction_->DecRef();
+  }
+
+  // Appends the source and destination operands for the register names
+  // given in the two vectors.
+  void AppendRegisterOperands(Instruction *inst,
+                              const std::vector<std::string> &sources,
+                              const std::vector<std::string> &destinations) {
+    for (auto &reg_name : sources) {
+      auto *reg = state_->GetRegister<RV64Register>(reg_name).first;
+      inst->AppendSource(reg->CreateSourceOperand());
+    }
+    for (auto &reg_name : destinations) {
+      auto *reg = state_->GetRegister<RV64Register>(reg_name).first;
+      inst->AppendDestination(reg->CreateDestinationOperand(0));
+    }
+  }
+
+  void AppendRegisterOperands(const std::vector<std::string> &sources,
+                              const std::vector<std::string> &destinations) {
+    AppendRegisterOperands(instruction_, sources, destinations);
+  }
+
+  // Appends immediate source operands with the given values.
+  template <typename T>
+  void AppendImmediateOperands(const std::vector<T> &values) {
+    for (auto value : values) {
+      auto *src = new ImmediateOperand<T>(value);
+      instruction_->AppendSource(src);
+    }
+  }
+
+  // Takes a vector of tuples of register names and values. Fetches each
+  // named register and sets it to the corresponding value.
+  template <typename T>
+  void SetRegisterValues(const std::vector<std::tuple<std::string, T>> values) {
+    for (auto &[reg_name, value] : values) {
+      auto *reg = state_->GetRegister<RV64Register>(reg_name).first;
+      reg->data_buffer()->template Set<T>(0, value);
+    }
+  }
+
+  // Initializes the semantic function of the instruction object.
+  void SetSemanticFunction(Instruction::SemanticFunction fcn) {
+    instruction_->set_semantic_function(fcn);
+  }
+
+  // Returns the value of the named register.
+  template <typename T>
+  T GetRegisterValue(absl::string_view reg_name) {
+    auto *reg = state_->GetRegister<RV64Register>(reg_name).first;
+    return reg->data_buffer()->Get<T>(0);
+  }
+
+  std::vector<std::string> GetRlistRegisters(int rlist) {
+    std::vector<std::string> rlist_regs;
+    if (rlist < 4) return rlist_regs;
+    rlist_regs.push_back(kX1);
+    if (rlist == 4) return rlist_regs;
+    rlist_regs.push_back(kX8);
+    if (rlist == 5) return rlist_regs;
+    rlist_regs.push_back(kX9);
+    if (rlist == 6) return rlist_regs;
+    rlist_regs.push_back(kX18);
+    if (rlist == 7) return rlist_regs;
+    rlist_regs.push_back(kX19);
+    if (rlist == 8) return rlist_regs;
+    rlist_regs.push_back(kX20);
+    if (rlist == 9) return rlist_regs;
+    rlist_regs.push_back(kX21);
+    if (rlist == 10) return rlist_regs;
+    rlist_regs.push_back(kX22);
+    if (rlist == 11) return rlist_regs;
+    rlist_regs.push_back(kX23);
+    if (rlist == 12) return rlist_regs;
+    rlist_regs.push_back(kX24);
+    if (rlist == 13) return rlist_regs;
+    rlist_regs.push_back(kX25);
+    if (rlist == 14) return rlist_regs;
+    rlist_regs.push_back(kX26);
+    rlist_regs.push_back(kX27);
+    return rlist_regs;
+  }
+
+  void ResetInstruction() {
+    instruction_->DecRef();
+    instruction_ = new Instruction(kInstAddress, state_);
+    instruction_->set_size(4);
+  }
+
+  void ResetMemory() {
+    auto *db = state_->db_factory()->Allocate<uint8_t>(0x2000);
+    std::memset(db->raw_ptr(), 0, 0x2000);
+    state_->StoreMemory(instruction_, 0, db);
+    db->DecRef();
+  }
+
+  FlatDemandMemory *memory_;
+  RiscVState *state_;
+  Instruction *instruction_;
+};
+
+constexpr int kNumReg[] = {0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13};
+constexpr char const *kRegMap[] = {kX1,  kX8,  kX9,  kX18, kX19, kX20, kX21,
+                                   kX22, kX23, kX24, kX25, kX26, kX27};
+constexpr int kStackAdjBase[] = {
+    0, 0, 0, 0, 16, 16, 32, 32, 48, 48, 64, 64, 80, 80, 96, 112,
+};
+
+// The push instruction pushes a set of up to 13 registers to the stack and
+// updates the stack pointer according to a combination of the rlist and spimm6
+// fields.
+TEST_F(RV64ZcInstructionTest, RV64ZCmpPush) {
+  // Initialize the registers that will be pushed to known value.
+  SetRegisterValues<uint64_t>({{kX1, 1},
+                               {kX8, 2},
+                               {kX9, 3},
+                               {kX18, 4},
+                               {kX19, 5},
+                               {kX20, 6},
+                               {kX21, 7},
+                               {kX22, 8},
+                               {kX23, 9},
+                               {kX24, 10},
+                               {kX25, 11},
+                               {kX26, 12},
+                               {kX27, 13}});
+  // Test each combination of rlist and spimm6.
+  for (int rlist = 4; rlist < 16; ++rlist) {
+    for (int spimm6 = 0; spimm6 < 64; spimm6 += 16) {
+      // Use x30 and x31 in place of spimm6 and rlist. This allows us to modify
+      // the values.
+      AppendRegisterOperands({kX2, kX30, kX31}, {kX2});
+      // Set the registers to the values we want.
+      SetRegisterValues<uint64_t>(
+          {{kX2, kMemAddress}, {kX30, spimm6}, {kX31, rlist}});
+      // Append the [rlist] registers.
+      AppendRegisterOperands(GetRlistRegisters(rlist), {});
+      SetSemanticFunction(&::mpact::sim::riscv::RV64::RiscVZCmpPush);
+      instruction_->Execute(nullptr);
+
+      // Fetch memory content.
+      auto *db = state_->db_factory()->Allocate<uint64_t>(13);
+      state_->LoadMemory(instruction_,
+                         kMemAddress - kNumReg[rlist] * sizeof(uint64_t), db,
+                         nullptr, nullptr);
+      auto db_span = db->Get<uint64_t>();
+      // Verify the values.
+      for (int i = 0; i < 13; ++i) {
+        if (i < kNumReg[rlist]) {
+          EXPECT_EQ(db_span[i], i + 1)
+              << "i: " << i << " rlist:" << rlist << " spimm6:" << spimm6;
+        } else {
+          EXPECT_EQ(db_span[i], 0)
+              << "i: " << i << " rlist:" << rlist << " spimm6:" << spimm6;
+        }
+      }
+      db->DecRef();
+      // Verify the stack pointer modification.
+      auto adjustment = kStackAdjBase[rlist] + spimm6;
+      EXPECT_EQ(GetRegisterValue<uint64_t>(kX2), kMemAddress - adjustment)
+          << "rlist: " << rlist << " spimm6: " << spimm6;
+
+      // Clear the instruction and memory.
+      ResetInstruction();
+      ResetMemory();
+    }
+  }
+}
+
+TEST_F(RV64ZcInstructionTest, RV64ZCmpPop) {
+  auto *db = state_->db_factory()->Allocate<uint64_t>(13);
+  // Test each combination of rlist and spimm6.
+  for (int rlist = 4; rlist < 16; ++rlist) {
+    for (int spimm6 = 0; spimm6 < 64; spimm6 += 16) {
+      // Append the [rlist] registers.
+      AppendRegisterOperands({}, GetRlistRegisters(rlist));
+      // Use x30 and x31 in place of spimm6 and rlist. This allows us to modify
+      // the values.
+      AppendRegisterOperands({kX2, kX30, kX31}, {kX2});
+      // Set the registers to the values we want.
+      SetRegisterValues<uint64_t>(
+          {{kX2, kMemAddress}, {kX30, spimm6}, {kX31, rlist}});
+      // Clear the registers that will be popped.
+      SetRegisterValues<uint64_t>({{kX1, 0},
+                                   {kX8, 0},
+                                   {kX9, 0},
+                                   {kX10, 0xdeadbeef},
+                                   {kX18, 0},
+                                   {kX19, 0},
+                                   {kX20, 0},
+                                   {kX21, 0},
+                                   {kX22, 0},
+                                   {kX23, 0},
+                                   {kX24, 0},
+                                   {kX25, 0},
+                                   {kX26, 0},
+                                   {kX27, 0}});
+      // Initialize memory. Write to the memory at addresses lower than the
+      // adjusted stack pointer (based on the adjustment for current rlist and
+      // spimm6).
+      auto adjusted_sp = kMemAddress + kStackAdjBase[rlist] + spimm6;
+      auto db_span = db->Get<uint64_t>();
+      for (int i = 0; i < 13; ++i) {
+        db_span[i] = i + 1;
+      }
+      state_->StoreMemory(instruction_,
+                          adjusted_sp - sizeof(uint64_t) * kNumReg[rlist], db);
+      SetSemanticFunction(&::mpact::sim::riscv::RV64::RiscVZCmpPop);
+      // Execute the instruction.
+      instruction_->Execute(nullptr);
+
+      // Iterate over the registers and verify the expected values.
+      for (int i = 0; i < 13; ++i) {
+        uint64_t value = GetRegisterValue<uint64_t>(kRegMap[i]);
+        if (i < kNumReg[rlist]) {
+          EXPECT_EQ(value, i + 1)
+              << "i: " << i << " rlist:" << rlist << " spimm6:" << spimm6;
+        } else {
+          EXPECT_EQ(value, 0)
+              << "i: " << i << " rlist:" << rlist << " spimm6:" << spimm6;
+        }
+      }
+      // Verify the stack pointer modification.
+      EXPECT_EQ(GetRegisterValue<uint64_t>(kX2), adjusted_sp)
+          << "rlist: " << rlist << " spimm6: " << spimm6;
+      // Verify that x10 is unchanged.
+      EXPECT_EQ(GetRegisterValue<uint64_t>(kX10), 0xdeadbeef)
+          << "rlist: " << rlist << " spimm6: " << spimm6;
+
+      // Clear the instruction.
+      ResetInstruction();
+    }
+  }
+  db->DecRef();
+}
+
+TEST_F(RV64ZcInstructionTest, RV64ZCmpPopRet) {
+  auto *db = state_->db_factory()->Allocate<uint64_t>(13);
+  // Test each combination of rlist and spimm6.
+  for (int rlist = 4; rlist < 16; ++rlist) {
+    for (int spimm6 = 0; spimm6 < 64; spimm6 += 16) {
+      // Append the [rlist] registers.
+      AppendRegisterOperands({}, GetRlistRegisters(rlist));
+      // Use x30 and x31 in place of spimm6 and rlist. This allows us to modify
+      // the values.
+      AppendRegisterOperands({kX2, kX30, kX31, kX1},
+                             {kX2, RiscVState::kPcName});
+      // Set the registers to the values we want.
+      SetRegisterValues<uint64_t>(
+          {{kX2, kMemAddress}, {kX30, spimm6}, {kX31, rlist}});
+      // Clear the registers that will be popped.
+      SetRegisterValues<uint64_t>({{kX1, 0},
+                                   {kX8, 0},
+                                   {kX9, 0},
+                                   {kX10, 0xdeadbeef},
+                                   {kX18, 0},
+                                   {kX19, 0},
+                                   {kX20, 0},
+                                   {kX21, 0},
+                                   {kX22, 0},
+                                   {kX23, 0},
+                                   {kX24, 0},
+                                   {kX25, 0},
+                                   {kX26, 0},
+                                   {kX27, 0}});
+      // Initialize memory. Write to the memory at addresses lower than the
+      // adjusted stack pointer (based on the adjustment for current rlist and
+      // spimm6).
+      auto adjusted_sp = kMemAddress + kStackAdjBase[rlist] + spimm6;
+      auto db_span = db->Get<uint64_t>();
+      for (int i = 0; i < 13; ++i) {
+        db_span[i] = i + 1;
+      }
+      state_->StoreMemory(instruction_,
+                          adjusted_sp - sizeof(uint64_t) * kNumReg[rlist], db);
+      SetSemanticFunction(&::mpact::sim::riscv::RV64::RiscVZCmpPopRet);
+      // Execute the instruction.
+      instruction_->Execute(nullptr);
+
+      // Iterate over the registers and verify the expected values.
+      for (int i = 0; i < 13; ++i) {
+        uint64_t value = GetRegisterValue<uint64_t>(kRegMap[i]);
+        if (i < kNumReg[rlist]) {
+          EXPECT_EQ(value, i + 1)
+              << "i: " << i << " rlist:" << rlist << " spimm6:" << spimm6;
+        } else {
+          EXPECT_EQ(value, 0)
+              << "i: " << i << " rlist:" << rlist << " spimm6:" << spimm6;
+        }
+      }
+      // Verify the stack pointer modification.
+      EXPECT_EQ(GetRegisterValue<uint64_t>(kX2), adjusted_sp)
+          << "rlist: " << rlist << " spimm6: " << spimm6;
+      // Verify that x10 is unchanged.
+      EXPECT_EQ(GetRegisterValue<uint64_t>(kX10), 0xdeadbeef)
+          << "rlist: " << rlist << " spimm6: " << spimm6;
+      // Verify that the PC is set to ra (X1), i.e., 1.
+      EXPECT_EQ(GetRegisterValue<uint64_t>(RiscVState::kPcName), 1);
+      // Clear the instruction.
+      ResetInstruction();
+    }
+  }
+  db->DecRef();
+}
+
+TEST_F(RV64ZcInstructionTest, RV64ZCmpPopRetz) {
+  auto *db = state_->db_factory()->Allocate<uint64_t>(13);
+  // Test each combination of rlist and spimm6.
+  for (int rlist = 4; rlist < 16; ++rlist) {
+    for (int spimm6 = 0; spimm6 < 64; spimm6 += 16) {
+      // Append the [rlist] registers.
+      AppendRegisterOperands({}, GetRlistRegisters(rlist));
+      // Use x30 and x31 in place of spimm6 and rlist. This allows us to modify
+      // the values.
+      AppendRegisterOperands({kX2, kX30, kX31, kX1},
+                             {kX2, kX10, RiscVState::kPcName});
+      // Set the registers to the values we want.
+      SetRegisterValues<uint64_t>(
+          {{kX2, kMemAddress}, {kX30, spimm6}, {kX31, rlist}});
+      // Clear the registers that will be popped.
+      SetRegisterValues<uint64_t>({{kX1, 0},
+                                   {kX8, 0},
+                                   {kX9, 0},
+                                   {kX10, 0xdeadbeef},
+                                   {kX18, 0},
+                                   {kX19, 0},
+                                   {kX20, 0},
+                                   {kX21, 0},
+                                   {kX22, 0},
+                                   {kX23, 0},
+                                   {kX24, 0},
+                                   {kX25, 0},
+                                   {kX26, 0},
+                                   {kX27, 0}});
+      // Initialize memory. Write to the memory at addresses lower than the
+      // adjusted stack pointer (based on the adjustment for current rlist and
+      // spimm6).
+      auto adjusted_sp = kMemAddress + kStackAdjBase[rlist] + spimm6;
+      auto db_span = db->Get<uint64_t>();
+      for (int i = 0; i < 13; ++i) {
+        db_span[i] = i + 1;
+      }
+      state_->StoreMemory(instruction_,
+                          adjusted_sp - sizeof(uint64_t) * kNumReg[rlist], db);
+      SetSemanticFunction(&::mpact::sim::riscv::RV64::RiscVZCmpPopRetz);
+      // Execute the instruction.
+      instruction_->Execute(nullptr);
+
+      // Iterate over the registers and verify the expected values.
+      for (int i = 0; i < 13; ++i) {
+        uint64_t value = GetRegisterValue<uint64_t>(kRegMap[i]);
+        if (i < kNumReg[rlist]) {
+          EXPECT_EQ(value, i + 1)
+              << "i: " << i << " rlist:" << rlist << " spimm6:" << spimm6;
+        } else {
+          EXPECT_EQ(value, 0)
+              << "i: " << i << " rlist:" << rlist << " spimm6:" << spimm6;
+        }
+      }
+      // Verify the stack pointer modification.
+      EXPECT_EQ(GetRegisterValue<uint64_t>(kX2), adjusted_sp)
+          << "rlist: " << rlist << " spimm6: " << spimm6;
+      // Verify that x10 is zeroed.
+      EXPECT_EQ(GetRegisterValue<uint64_t>(kX10), 0x0)
+          << "rlist: " << rlist << " spimm6: " << spimm6;
+      // Verify that the PC is set to ra (X1), i.e., 1.
+      EXPECT_EQ(GetRegisterValue<uint64_t>(RiscVState::kPcName), 1);
+      // Clear the instruction.
+      ResetInstruction();
+    }
+  }
+  db->DecRef();
+}
+
+TEST_F(RV64ZcInstructionTest, RV64ZMvTwoRegs) {
+  AppendRegisterOperands({kX1, kX2}, {kX10, kX11});
+  SetRegisterValues<uint64_t>({{kX1, 1}, {kX2, 2}, {kX10, 10}, {kX11, 11}});
+  SetSemanticFunction(&::mpact::sim::riscv::RV64::RiscVZCmpMvTwoRegs);
+  instruction_->Execute(nullptr);
+
+  EXPECT_EQ(GetRegisterValue<uint64_t>(kX10), 1);
+  EXPECT_EQ(GetRegisterValue<uint64_t>(kX11), 2);
+}
+
+TEST_F(RV64ZcInstructionTest, RV64ZCmtJt) {
+  // Use a register instead of the immediate for the index.
+  AppendRegisterOperands({kX10}, {RiscVState::kPcName});
+  SetSemanticFunction(&::mpact::sim::riscv::RV64::RiscVZCmtJt);
+  // Indices have to be less than 32.
+  for (int i = 0; i < 16; ++i) {
+    SetRegisterValues<uint64_t>({{kX10, i}});
+    instruction_->Execute(nullptr);
+    EXPECT_EQ(GetRegisterValue<uint64_t>(RiscVState::kPcName),
+              0x8000 + i * sizeof(uint64_t))
+        << "i: " << i;
+  }
+}
+
+TEST_F(RV64ZcInstructionTest, RV64ZCmtJalt) {
+  // Use a register instead of the immediate for the index.
+  AppendRegisterOperands({kX10}, {RiscVState::kPcName, kX1});
+  SetSemanticFunction(&::mpact::sim::riscv::RV64::RiscVZCmtJalt);
+  // Indices have to be greater or equal to 32.
+  for (int i = 32; i < 48; ++i) {
+    SetRegisterValues<uint64_t>({{kX10, i}, {kX1, 0}});
+    instruction_->Execute(nullptr);
+    EXPECT_EQ(GetRegisterValue<uint64_t>(RiscVState::kPcName),
+              0x8000 + i * sizeof(uint64_t))
+        << "i: " << i;
+    EXPECT_EQ(GetRegisterValue<uint64_t>(kX1),
+              instruction_->address() + instruction_->size());
+  }
+}
+
+}  // namespace
diff --git a/riscv/test/riscv_zc_instructions_test.cc b/riscv/test/riscv_zc_instructions_test.cc
new file mode 100644
index 0000000..4412812
--- /dev/null
+++ b/riscv/test/riscv_zc_instructions_test.cc
@@ -0,0 +1,503 @@
+// Copyright 2024 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 "riscv/riscv_zc_instructions.h"
+
+#include <cstdint>
+#include <cstring>
+#include <string>
+#include <tuple>
+#include <vector>
+
+#include "absl/strings/string_view.h"
+#include "googlemock/include/gmock/gmock.h"
+#include "mpact/sim/generic/immediate_operand.h"
+#include "mpact/sim/generic/instruction.h"
+#include "mpact/sim/util/memory/flat_demand_memory.h"
+#include "riscv/riscv_register.h"
+#include "riscv/riscv_state.h"
+
+// This file contains the unit tests for the 32 bit Zcmp* instructions that
+// are not covered elsewhere.
+
+namespace {
+
+using ::mpact::sim::generic::ImmediateOperand;
+using ::mpact::sim::generic::Instruction;
+using ::mpact::sim::riscv::RiscVState;
+using ::mpact::sim::riscv::RiscVXlen;
+using ::mpact::sim::riscv::RV32Register;
+using ::mpact::sim::util::FlatDemandMemory;
+
+constexpr uint32_t kInstAddress = 0x2468;
+constexpr char kX1[] = "x1";
+constexpr char kX2[] = "x2";
+constexpr char kX8[] = "x8";
+constexpr char kX9[] = "x9";
+constexpr char kX10[] = "x10";
+constexpr char kX11[] = "x11";
+constexpr char kX18[] = "x18";
+constexpr char kX19[] = "x19";
+constexpr char kX20[] = "x20";
+constexpr char kX21[] = "x21";
+constexpr char kX22[] = "x22";
+constexpr char kX23[] = "x23";
+constexpr char kX24[] = "x24";
+constexpr char kX25[] = "x25";
+constexpr char kX26[] = "x26";
+constexpr char kX27[] = "x27";
+constexpr char kX30[] = "x30";
+constexpr char kX31[] = "x31";
+constexpr uint32_t kMemAddress = 0x1000;
+
+// The test fixture allocates a machine state object and an instruction object.
+// It also contains convenience methods for interacting with the instruction
+// object in a more short hand form.
+class RV32ZcInstructionTest : public testing::Test {
+ public:
+  RV32ZcInstructionTest() {
+    memory_ = new FlatDemandMemory();
+    state_ = new RiscVState("test", RiscVXlen::RV32, memory_);
+    instruction_ = new Instruction(kInstAddress, state_);
+    instruction_->set_size(4);
+    // Set the jump table address to 0x4000.
+    state_->jvt()->Set(static_cast<uint32_t>(0x4000));
+    auto *db = state_->db_factory()->Allocate<uint32_t>(256);
+    auto db_span = db->Get<uint32_t>();
+    for (auto i = 0; i < 256; ++i) {
+      db_span[i] = 0x8000 + i * sizeof(uint64_t);
+    }
+    state_->StoreMemory(nullptr, 0x4000, db);
+    db->DecRef();
+  }
+
+  ~RV32ZcInstructionTest() override {
+    delete memory_;
+    delete state_;
+    instruction_->DecRef();
+  }
+
+  // Appends the source and destination operands for the register names
+  // given in the two vectors.
+  void AppendRegisterOperands(Instruction *inst,
+                              const std::vector<std::string> &sources,
+                              const std::vector<std::string> &destinations) {
+    for (auto &reg_name : sources) {
+      auto *reg = state_->GetRegister<RV32Register>(reg_name).first;
+      inst->AppendSource(reg->CreateSourceOperand());
+    }
+    for (auto &reg_name : destinations) {
+      auto *reg = state_->GetRegister<RV32Register>(reg_name).first;
+      inst->AppendDestination(reg->CreateDestinationOperand(0));
+    }
+  }
+
+  void AppendRegisterOperands(const std::vector<std::string> &sources,
+                              const std::vector<std::string> &destinations) {
+    AppendRegisterOperands(instruction_, sources, destinations);
+  }
+
+  // Appends immediate source operands with the given values.
+  template <typename T>
+  void AppendImmediateOperands(const std::vector<T> &values) {
+    for (auto value : values) {
+      auto *src = new ImmediateOperand<T>(value);
+      instruction_->AppendSource(src);
+    }
+  }
+
+  // Takes a vector of tuples of register names and values. Fetches each
+  // named register and sets it to the corresponding value.
+  template <typename T>
+  void SetRegisterValues(const std::vector<std::tuple<std::string, T>> values) {
+    for (auto &[reg_name, value] : values) {
+      auto *reg = state_->GetRegister<RV32Register>(reg_name).first;
+      reg->data_buffer()->template Set<T>(0, value);
+    }
+  }
+
+  // Initializes the semantic function of the instruction object.
+  void SetSemanticFunction(Instruction::SemanticFunction fcn) {
+    instruction_->set_semantic_function(fcn);
+  }
+
+  // Returns the value of the named register.
+  template <typename T>
+  T GetRegisterValue(absl::string_view reg_name) {
+    auto *reg = state_->GetRegister<RV32Register>(reg_name).first;
+    return reg->data_buffer()->Get<T>(0);
+  }
+
+  std::vector<std::string> GetRlistRegisters(int rlist) {
+    std::vector<std::string> rlist_regs;
+    if (rlist < 4) return rlist_regs;
+    rlist_regs.push_back(kX1);
+    if (rlist == 4) return rlist_regs;
+    rlist_regs.push_back(kX8);
+    if (rlist == 5) return rlist_regs;
+    rlist_regs.push_back(kX9);
+    if (rlist == 6) return rlist_regs;
+    rlist_regs.push_back(kX18);
+    if (rlist == 7) return rlist_regs;
+    rlist_regs.push_back(kX19);
+    if (rlist == 8) return rlist_regs;
+    rlist_regs.push_back(kX20);
+    if (rlist == 9) return rlist_regs;
+    rlist_regs.push_back(kX21);
+    if (rlist == 10) return rlist_regs;
+    rlist_regs.push_back(kX22);
+    if (rlist == 11) return rlist_regs;
+    rlist_regs.push_back(kX23);
+    if (rlist == 12) return rlist_regs;
+    rlist_regs.push_back(kX24);
+    if (rlist == 13) return rlist_regs;
+    rlist_regs.push_back(kX25);
+    if (rlist == 14) return rlist_regs;
+    rlist_regs.push_back(kX26);
+    rlist_regs.push_back(kX27);
+    return rlist_regs;
+  }
+
+  void ResetInstruction() {
+    instruction_->DecRef();
+    instruction_ = new Instruction(kInstAddress, state_);
+    instruction_->set_size(4);
+  }
+
+  void ResetMemory() {
+    auto *db = state_->db_factory()->Allocate<uint8_t>(0x2000);
+    std::memset(db->raw_ptr(), 0, 0x2000);
+    state_->StoreMemory(instruction_, 0, db);
+    db->DecRef();
+  }
+
+  FlatDemandMemory *memory_;
+  RiscVState *state_;
+  Instruction *instruction_;
+};
+
+constexpr int kNumReg[] = {0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13};
+constexpr char const *kRegMap[] = {kX1,  kX8,  kX9,  kX18, kX19, kX20, kX21,
+                                   kX22, kX23, kX24, kX25, kX26, kX27};
+constexpr int kStackAdjBase[] = {
+    0, 0, 0, 0, 16, 16, 16, 16, 32, 32, 32, 32, 48, 48, 48, 64,
+};
+
+// The push instruction pushes a set of up to 13 registers to the stack and
+// updates the stack pointer according to a combination of the rlist and spimm6
+// fields.
+TEST_F(RV32ZcInstructionTest, RV32ZCmpPush) {
+  // Initialize the registers that will be pushed to known value.
+  SetRegisterValues<uint32_t>({{kX1, 1},
+                               {kX8, 2},
+                               {kX9, 3},
+                               {kX18, 4},
+                               {kX19, 5},
+                               {kX20, 6},
+                               {kX21, 7},
+                               {kX22, 8},
+                               {kX23, 9},
+                               {kX24, 10},
+                               {kX25, 11},
+                               {kX26, 12},
+                               {kX27, 13}});
+  // Test each combination of rlist and spimm6.
+  for (int rlist = 4; rlist < 16; ++rlist) {
+    for (int spimm6 = 0; spimm6 < 64; spimm6 += 16) {
+      // Use x30 and x31 in place of spimm6 and rlist. This allows us to modify
+      // the values.
+      AppendRegisterOperands({kX2, kX30, kX31}, {kX2});
+      // Set the registers to the values we want.
+      SetRegisterValues<uint32_t>(
+          {{kX2, kMemAddress}, {kX30, spimm6}, {kX31, rlist}});
+      // Append the [rlist] registers.
+      AppendRegisterOperands(GetRlistRegisters(rlist), {});
+      SetSemanticFunction(&::mpact::sim::riscv::RV32::RiscVZCmpPush);
+      instruction_->Execute(nullptr);
+
+      // Fetch memory content.
+      auto *db = state_->db_factory()->Allocate<uint32_t>(13);
+      state_->LoadMemory(instruction_,
+                         kMemAddress - kNumReg[rlist] * sizeof(uint32_t), db,
+                         nullptr, nullptr);
+      auto db_span = db->Get<uint32_t>();
+      // Verify the values.
+      for (int i = 0; i < 13; ++i) {
+        if (i < kNumReg[rlist]) {
+          EXPECT_EQ(db_span[i], i + 1)
+              << "i: " << i << " rlist:" << rlist << " spimm6:" << spimm6;
+        } else {
+          EXPECT_EQ(db_span[i], 0)
+              << "i: " << i << " rlist:" << rlist << " spimm6:" << spimm6;
+        }
+      }
+      db->DecRef();
+      // Verify the stack pointer modification.
+      auto adjustment = kStackAdjBase[rlist] + spimm6;
+      EXPECT_EQ(GetRegisterValue<uint32_t>(kX2), kMemAddress - adjustment)
+          << "rlist: " << rlist << " spimm6: " << spimm6;
+
+      // Clear the instruction and memory.
+      ResetInstruction();
+      ResetMemory();
+    }
+  }
+}
+
+TEST_F(RV32ZcInstructionTest, RV32ZCmpPop) {
+  auto *db = state_->db_factory()->Allocate<uint32_t>(13);
+  // Test each combination of rlist and spimm6.
+  for (int rlist = 4; rlist < 16; ++rlist) {
+    for (int spimm6 = 0; spimm6 < 64; spimm6 += 16) {
+      // Append the [rlist] registers.
+      AppendRegisterOperands({}, GetRlistRegisters(rlist));
+      // Use x30 and x31 in place of spimm6 and rlist. This allows us to modify
+      // the values.
+      AppendRegisterOperands({kX2, kX30, kX31}, {kX2});
+      // Set the registers to the values we want.
+      SetRegisterValues<uint32_t>(
+          {{kX2, kMemAddress}, {kX30, spimm6}, {kX31, rlist}});
+      // Clear the registers that will be popped.
+      SetRegisterValues<uint32_t>({{kX1, 0},
+                                   {kX8, 0},
+                                   {kX9, 0},
+                                   {kX10, 0xdeadbeef},
+                                   {kX18, 0},
+                                   {kX19, 0},
+                                   {kX20, 0},
+                                   {kX21, 0},
+                                   {kX22, 0},
+                                   {kX23, 0},
+                                   {kX24, 0},
+                                   {kX25, 0},
+                                   {kX26, 0},
+                                   {kX27, 0}});
+      // Initialize memory. Write to the memory at addresses lower than the
+      // adjusted stack pointer (based on the adjustment for current rlist and
+      // spimm6).
+      auto adjusted_sp = kMemAddress + kStackAdjBase[rlist] + spimm6;
+      auto db_span = db->Get<uint32_t>();
+      for (int i = 0; i < 13; ++i) {
+        db_span[i] = i + 1;
+      }
+      state_->StoreMemory(instruction_,
+                          adjusted_sp - sizeof(uint32_t) * kNumReg[rlist], db);
+      SetSemanticFunction(&::mpact::sim::riscv::RV32::RiscVZCmpPop);
+      // Execute the instruction.
+      instruction_->Execute(nullptr);
+
+      // Iterate over the registers and verify the expected values.
+      for (int i = 0; i < 13; ++i) {
+        uint32_t value = GetRegisterValue<uint32_t>(kRegMap[i]);
+        if (i < kNumReg[rlist]) {
+          EXPECT_EQ(value, i + 1)
+              << "i: " << i << " rlist:" << rlist << " spimm6:" << spimm6;
+        } else {
+          EXPECT_EQ(value, 0)
+              << "i: " << i << " rlist:" << rlist << " spimm6:" << spimm6;
+        }
+      }
+      // Verify the stack pointer modification.
+      EXPECT_EQ(GetRegisterValue<uint32_t>(kX2), adjusted_sp)
+          << "rlist: " << rlist << " spimm6: " << spimm6;
+      // Verify that x10 is unchanged.
+      EXPECT_EQ(GetRegisterValue<uint32_t>(kX10), 0xdeadbeef)
+          << "rlist: " << rlist << " spimm6: " << spimm6;
+
+      // Clear the instruction.
+      ResetInstruction();
+    }
+  }
+  db->DecRef();
+}
+
+TEST_F(RV32ZcInstructionTest, RV32ZCmpPopRet) {
+  auto *db = state_->db_factory()->Allocate<uint32_t>(13);
+  // Test each combination of rlist and spimm6.
+  for (int rlist = 4; rlist < 16; ++rlist) {
+    for (int spimm6 = 0; spimm6 < 64; spimm6 += 16) {
+      // Append the [rlist] registers.
+      AppendRegisterOperands({}, GetRlistRegisters(rlist));
+      // Use x30 and x31 in place of spimm6 and rlist. This allows us to modify
+      // the values.
+      AppendRegisterOperands({kX2, kX30, kX31, kX1},
+                             {kX2, RiscVState::kPcName});
+      // Set the registers to the values we want.
+      SetRegisterValues<uint32_t>(
+          {{kX2, kMemAddress}, {kX30, spimm6}, {kX31, rlist}});
+      // Clear the registers that will be popped.
+      SetRegisterValues<uint32_t>({{kX1, 0},
+                                   {kX8, 0},
+                                   {kX9, 0},
+                                   {kX10, 0xdeadbeef},
+                                   {kX18, 0},
+                                   {kX19, 0},
+                                   {kX20, 0},
+                                   {kX21, 0},
+                                   {kX22, 0},
+                                   {kX23, 0},
+                                   {kX24, 0},
+                                   {kX25, 0},
+                                   {kX26, 0},
+                                   {kX27, 0}});
+      // Initialize memory. Write to the memory at addresses lower than the
+      // adjusted stack pointer (based on the adjustment for current rlist and
+      // spimm6).
+      auto adjusted_sp = kMemAddress + kStackAdjBase[rlist] + spimm6;
+      auto db_span = db->Get<uint32_t>();
+      for (int i = 0; i < 13; ++i) {
+        db_span[i] = i + 1;
+      }
+      state_->StoreMemory(instruction_,
+                          adjusted_sp - sizeof(uint32_t) * kNumReg[rlist], db);
+      SetSemanticFunction(&::mpact::sim::riscv::RV32::RiscVZCmpPopRet);
+      // Execute the instruction.
+      instruction_->Execute(nullptr);
+
+      // Iterate over the registers and verify the expected values.
+      for (int i = 0; i < 13; ++i) {
+        uint32_t value = GetRegisterValue<uint32_t>(kRegMap[i]);
+        if (i < kNumReg[rlist]) {
+          EXPECT_EQ(value, i + 1)
+              << "i: " << i << " rlist:" << rlist << " spimm6:" << spimm6;
+        } else {
+          EXPECT_EQ(value, 0)
+              << "i: " << i << " rlist:" << rlist << " spimm6:" << spimm6;
+        }
+      }
+      // Verify the stack pointer modification.
+      EXPECT_EQ(GetRegisterValue<uint32_t>(kX2), adjusted_sp)
+          << "rlist: " << rlist << " spimm6: " << spimm6;
+      // Verify that x10 is unchanged.
+      EXPECT_EQ(GetRegisterValue<uint32_t>(kX10), 0xdeadbeef)
+          << "rlist: " << rlist << " spimm6: " << spimm6;
+      // Verify that the PC is set to ra (X1), i.e., 1.
+      EXPECT_EQ(GetRegisterValue<uint32_t>(RiscVState::kPcName), 1);
+      // Clear the instruction.
+      ResetInstruction();
+    }
+  }
+  db->DecRef();
+}
+
+TEST_F(RV32ZcInstructionTest, RV32ZCmpPopRetz) {
+  auto *db = state_->db_factory()->Allocate<uint32_t>(13);
+  // Test each combination of rlist and spimm6.
+  for (int rlist = 4; rlist < 16; ++rlist) {
+    for (int spimm6 = 0; spimm6 < 64; spimm6 += 16) {
+      // Append the [rlist] registers.
+      AppendRegisterOperands({}, GetRlistRegisters(rlist));
+      // Use x30 and x31 in place of spimm6 and rlist. This allows us to modify
+      // the values.
+      AppendRegisterOperands({kX2, kX30, kX31, kX1},
+                             {kX2, kX10, RiscVState::kPcName});
+      // Set the registers to the values we want.
+      SetRegisterValues<uint32_t>(
+          {{kX2, kMemAddress}, {kX30, spimm6}, {kX31, rlist}});
+      // Clear the registers that will be popped.
+      SetRegisterValues<uint32_t>({{kX1, 0},
+                                   {kX8, 0},
+                                   {kX9, 0},
+                                   {kX10, 0xdeadbeef},
+                                   {kX18, 0},
+                                   {kX19, 0},
+                                   {kX20, 0},
+                                   {kX21, 0},
+                                   {kX22, 0},
+                                   {kX23, 0},
+                                   {kX24, 0},
+                                   {kX25, 0},
+                                   {kX26, 0},
+                                   {kX27, 0}});
+      // Initialize memory. Write to the memory at addresses lower than the
+      // adjusted stack pointer (based on the adjustment for current rlist and
+      // spimm6).
+      auto adjusted_sp = kMemAddress + kStackAdjBase[rlist] + spimm6;
+      auto db_span = db->Get<uint32_t>();
+      for (int i = 0; i < 13; ++i) {
+        db_span[i] = i + 1;
+      }
+      state_->StoreMemory(instruction_,
+                          adjusted_sp - sizeof(uint32_t) * kNumReg[rlist], db);
+      SetSemanticFunction(&::mpact::sim::riscv::RV32::RiscVZCmpPopRetz);
+      // Execute the instruction.
+      instruction_->Execute(nullptr);
+
+      // Iterate over the registers and verify the expected values.
+      for (int i = 0; i < 13; ++i) {
+        uint32_t value = GetRegisterValue<uint32_t>(kRegMap[i]);
+        if (i < kNumReg[rlist]) {
+          EXPECT_EQ(value, i + 1)
+              << "i: " << i << " rlist:" << rlist << " spimm6:" << spimm6;
+        } else {
+          EXPECT_EQ(value, 0)
+              << "i: " << i << " rlist:" << rlist << " spimm6:" << spimm6;
+        }
+      }
+      // Verify the stack pointer modification.
+      EXPECT_EQ(GetRegisterValue<uint32_t>(kX2), adjusted_sp)
+          << "rlist: " << rlist << " spimm6: " << spimm6;
+      // Verify that x10 is zeroed.
+      EXPECT_EQ(GetRegisterValue<uint32_t>(kX10), 0x0)
+          << "rlist: " << rlist << " spimm6: " << spimm6;
+      // Verify that the PC is set to ra (X1), i.e., 1.
+      EXPECT_EQ(GetRegisterValue<uint32_t>(RiscVState::kPcName), 1);
+      // Clear the instruction.
+      ResetInstruction();
+    }
+  }
+  db->DecRef();
+}
+
+TEST_F(RV32ZcInstructionTest, RV32ZMvTwoRegs) {
+  AppendRegisterOperands({kX1, kX2}, {kX10, kX11});
+  SetRegisterValues<uint32_t>({{kX1, 1}, {kX2, 2}, {kX10, 10}, {kX11, 11}});
+  SetSemanticFunction(&::mpact::sim::riscv::RV32::RiscVZCmpMvTwoRegs);
+  instruction_->Execute(nullptr);
+
+  EXPECT_EQ(GetRegisterValue<uint32_t>(kX10), 1);
+  EXPECT_EQ(GetRegisterValue<uint32_t>(kX11), 2);
+}
+
+TEST_F(RV32ZcInstructionTest, RV32ZCmtJt) {
+  // Use a register instead of the immediate for the index.
+  AppendRegisterOperands({kX10}, {RiscVState::kPcName});
+  SetSemanticFunction(&::mpact::sim::riscv::RV32::RiscVZCmtJt);
+  // Indices have to be less than 32.
+  for (int i = 0; i < 16; ++i) {
+    SetRegisterValues<uint32_t>({{kX10, i}});
+    instruction_->Execute(nullptr);
+    EXPECT_EQ(GetRegisterValue<uint32_t>(RiscVState::kPcName),
+              0x8000 + i * sizeof(uint64_t))
+        << "i: " << i;
+  }
+}
+
+TEST_F(RV32ZcInstructionTest, RV32ZCmtJalt) {
+  // Use a register instead of the immediate for the index.
+  AppendRegisterOperands({kX10}, {RiscVState::kPcName, kX1});
+  SetSemanticFunction(&::mpact::sim::riscv::RV32::RiscVZCmtJalt);
+  // Indices have to be greater or equal to 32.
+  for (int i = 32; i < 48; ++i) {
+    SetRegisterValues<uint32_t>({{kX10, i}, {kX1, 0}});
+    instruction_->Execute(nullptr);
+    EXPECT_EQ(GetRegisterValue<uint32_t>(RiscVState::kPcName),
+              0x8000 + i * sizeof(uint64_t))
+        << "i: " << i;
+    EXPECT_EQ(GetRegisterValue<uint32_t>(kX1),
+              instruction_->address() + instruction_->size());
+  }
+}
+
+}  // namespace