| // 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); |
| auto* state = static_cast<RiscVState*>(inst->state()); |
| state->set_branch(true); |
| } |
| |
| 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); |
| auto* state = static_cast<RiscVState*>(inst->state()); |
| state->set_branch(true); |
| } |
| |
| 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); |
| state->set_branch(true); |
| } |
| |
| } // 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); |
| auto* state = static_cast<RiscVState*>(inst->state()); |
| state->set_branch(true); |
| } |
| |
| 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); |
| auto* state = static_cast<RiscVState*>(inst->state()); |
| state->set_branch(true); |
| } |
| |
| 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); |
| state->set_branch(true); |
| } |
| |
| } // 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 |