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 ®_name : sources) { + auto *reg = state_->GetRegister<RV64Register>(reg_name).first; + inst->AppendSource(reg->CreateSourceOperand()); + } + for (auto ®_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 ®_name : sources) { + auto *reg = state_->GetRegister<RV32Register>(reg_name).first; + inst->AppendSource(reg->CreateSourceOperand()); + } + for (auto ®_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