diff --git a/configs/DEMO_RISCV.yaml b/configs/DEMO_RISCV.yaml index 1cd86d8646..2ca8c35c36 100644 --- a/configs/DEMO_RISCV.yaml +++ b/configs/DEMO_RISCV.yaml @@ -4,6 +4,7 @@ Core: ISA: rv64 + Compressed: True Simulation-Mode: outoforder Clock-Frequency-GHz: 2.5 Timer-Frequency-MHz: 200 diff --git a/docs/sphinx/developer/arch/supported/riscv.rst b/docs/sphinx/developer/arch/supported/riscv.rst index 51157075d8..f0467f86b9 100644 --- a/docs/sphinx/developer/arch/supported/riscv.rst +++ b/docs/sphinx/developer/arch/supported/riscv.rst @@ -1,7 +1,7 @@ RISCV ======= -SimEng provides an almost complete implementation of the rv64imafd architecture, as well as being capable of handling some supervisor call (syscall) exceptions via basic system call emulation. This is sufficient to run many simple single threaded programs that have been statically compiled with the standard library. +SimEng provides an almost complete implementation of the rv64imafdc architecture, as well as being capable of handling some supervisor call (syscall) exceptions via basic system call emulation. This is sufficient to run many simple single threaded programs that have been statically compiled with the standard library. .. contents:: Contents @@ -57,6 +57,8 @@ Adding execution behaviour The process for adding a new instruction is very similar to that of :ref:`AArch64 `, by adding a new, uniquely identified entry to ``src/lib/arch/riscv/Instruction_execute.cc``. +Compressed instructions are treated in the same way as pseudoinstructions. By design they can be expanded to full instructions from the base and floating point extensions. A new case should be added to the switch statement in ``InstructionMetadata`` to perform the relevant adjustment to the metadata. The instruction can then be allowed to flow through the pipeline - no new execute case is necessary. + Zero registers ************** diff --git a/docs/sphinx/user/configuring_simeng.rst b/docs/sphinx/user/configuring_simeng.rst index e60a0a7372..a021369ea6 100644 --- a/docs/sphinx/user/configuring_simeng.rst +++ b/docs/sphinx/user/configuring_simeng.rst @@ -52,6 +52,9 @@ Vector-Length (Only in use when ISA is ``AArch64``) Streaming-Vector-Length (Only in use when ISA is ``AArch64``) The vector length used by instructions belonging to Arm's Scalable Matrix Extension. Although the architecturally valid vector lengths are powers of 2 between 128 and 2048 inclusive, the supported vector lengths are those between 128 and 2048 in increments of 128. +Compressed (Only in use when ISA is ``rv64``) + Enables the RISC-V compressed extension. If set to false and compressed instructions are supplied, a misaligned program counter exception is usually thrown. + Fetch ----- diff --git a/src/include/simeng/arch/Architecture.hh b/src/include/simeng/arch/Architecture.hh index a323701fde..12e95687c2 100644 --- a/src/include/simeng/arch/Architecture.hh +++ b/src/include/simeng/arch/Architecture.hh @@ -74,6 +74,9 @@ class Architecture { /** Returns the maximum size of a valid instruction in bytes. */ virtual uint8_t getMaxInstructionSize() const = 0; + /** Returns the minimum size of a valid instruction in bytes. */ + virtual uint8_t getMinInstructionSize() const = 0; + /** Updates System registers of any system-based timers. */ virtual void updateSystemTimerRegisters(RegisterFileSet* regFile, const uint64_t iterations) const = 0; diff --git a/src/include/simeng/arch/aarch64/Architecture.hh b/src/include/simeng/arch/aarch64/Architecture.hh index 515ce26657..196af33ee7 100644 --- a/src/include/simeng/arch/aarch64/Architecture.hh +++ b/src/include/simeng/arch/aarch64/Architecture.hh @@ -47,6 +47,9 @@ class Architecture : public arch::Architecture { /** Returns the maximum size of a valid instruction in bytes. */ uint8_t getMaxInstructionSize() const override; + /** Returns the minimum size of a valid instruction in bytes. */ + uint8_t getMinInstructionSize() const override; + /** Updates System registers of any system-based timers. */ void updateSystemTimerRegisters(RegisterFileSet* regFile, const uint64_t iterations) const override; diff --git a/src/include/simeng/arch/aarch64/Instruction.hh b/src/include/simeng/arch/aarch64/Instruction.hh index 5d434162ad..315a555a00 100644 --- a/src/include/simeng/arch/aarch64/Instruction.hh +++ b/src/include/simeng/arch/aarch64/Instruction.hh @@ -437,8 +437,8 @@ class Instruction : public simeng::Instruction { /** The current exception state of this instruction. */ InstructionException exception_ = InstructionException::None; - /** The number of operands that have not yet had values supplied. Used to - * determine execution readiness. */ + /** The number of source operands that have not yet had values supplied. Used + * to determine execution readiness. */ uint16_t sourceOperandsPending_ = 0; /** Is the micro-operation opcode of the instruction, where appropriate. */ diff --git a/src/include/simeng/arch/riscv/ArchInfo.hh b/src/include/simeng/arch/riscv/ArchInfo.hh index b721984ae7..039d8e6ac5 100644 --- a/src/include/simeng/arch/riscv/ArchInfo.hh +++ b/src/include/simeng/arch/riscv/ArchInfo.hh @@ -19,6 +19,14 @@ typedef enum riscv_sysreg { } riscv_sysreg; +// A struct of RISC-V specific constants +namespace constantsPool { +const uint8_t addressAlignMask = 0x3; +const uint8_t addressAlignMaskCompressed = 0x1; +const uint8_t minInstWidthBytes = 4; +const uint8_t minInstWidthBytesCompressed = 2; +}; // namespace constantsPool + /** A class to hold and generate riscv specific architecture configuration * options. */ class ArchInfo : public simeng::arch::ArchInfo { diff --git a/src/include/simeng/arch/riscv/Architecture.hh b/src/include/simeng/arch/riscv/Architecture.hh index d8fdf7c4f0..322928347a 100644 --- a/src/include/simeng/arch/riscv/Architecture.hh +++ b/src/include/simeng/arch/riscv/Architecture.hh @@ -22,8 +22,8 @@ class Architecture : public arch::Architecture { ~Architecture(); /** Pre-decode instruction memory into a macro-op of `Instruction` - * instances. Returns the number of bytes consumed to produce it (always 4), - * and writes into the supplied macro-op vector. */ + * instances. Returns the number of bytes consumed to produce it (0 if + * failure), and writes into the supplied macro-op vector. */ uint8_t predecode(const void* ptr, uint16_t bytesAvailable, uint64_t instructionAddress, MacroOp& output) const override; @@ -45,6 +45,9 @@ class Architecture : public arch::Architecture { /** Returns the maximum size of a valid instruction in bytes. */ uint8_t getMaxInstructionSize() const override; + /** Returns the minimum size of a valid instruction in bytes. */ + uint8_t getMinInstructionSize() const override; + /** Updates System registers of any system-based timers. */ void updateSystemTimerRegisters(RegisterFileSet* regFile, const uint64_t iterations) const override; @@ -68,6 +71,12 @@ class Architecture : public arch::Architecture { /** System Register of Processor Cycle Counter. */ simeng::Register cycleSystemReg_; + + /** A mask used to determine if an address has the correct byte alignment */ + uint8_t addressAlignmentMask_; + + /** Minimum number of bytes that can represent an instruction */ + uint8_t minInsnLength_; }; } // namespace riscv diff --git a/src/include/simeng/arch/riscv/Instruction.hh b/src/include/simeng/arch/riscv/Instruction.hh index bf597a23a6..888900ba18 100644 --- a/src/include/simeng/arch/riscv/Instruction.hh +++ b/src/include/simeng/arch/riscv/Instruction.hh @@ -233,6 +233,10 @@ class Instruction : public simeng::Instruction { * `sourceRegisters` entry. */ std::array sourceValues_; + /** The immediate source operand for which there is only ever one. Remains 0 + * if unused. */ + int64_t sourceImm_ = 0; + /** An array of generated output results. Each entry corresponds to a * `destinationRegisters` entry. */ std::array results_; @@ -240,8 +244,8 @@ class Instruction : public simeng::Instruction { /** The current exception state of this instruction. */ InstructionException exception_ = InstructionException::None; - /** The number of operands that have not yet had values supplied. Used to - * determine execution readiness. */ + /** The number of source operands that have not yet had values supplied. Used + * to determine execution readiness. */ uint16_t sourceOperandsPending_ = 0; /** Used to denote what type of instruction this is. Utilises the constants in diff --git a/src/include/simeng/kernel/LinuxProcess.hh b/src/include/simeng/kernel/LinuxProcess.hh index 47997f8d03..b256f1cfdd 100644 --- a/src/include/simeng/kernel/LinuxProcess.hh +++ b/src/include/simeng/kernel/LinuxProcess.hh @@ -40,7 +40,7 @@ uint64_t alignToBoundary(uint64_t value, uint64_t boundary); * * The constructed process follows a typical layout: * - * |---------------| <- start of stack + * |---------------| <- start/bottom of stack * | Stack | stack grows downwards * |-v-----------v-| * | | @@ -76,7 +76,7 @@ class LinuxProcess { /** Get the address of the start of the heap region. */ uint64_t getHeapStart() const; - /** Get the address of the top of the stack. */ + /** Get the address of the bottom of the stack. */ uint64_t getStackStart() const; /** Get the address of the start of the mmap region. */ @@ -94,7 +94,7 @@ class LinuxProcess { /** Get the entry point. */ uint64_t getEntryPoint() const; - /** Get the initial stack pointer address. */ + /** Get the initial stack pointer. */ uint64_t getInitialStackPointer() const; /** Get the path of the executable. */ @@ -134,7 +134,7 @@ class LinuxProcess { /** The page size of the process memory. */ const uint64_t pageSize_ = 4096; - /** The address of the stack pointer. */ + /** The address of the head/top of the stack */ uint64_t stackPointer_; /** The process image size. */ diff --git a/src/include/simeng/pipeline/FetchUnit.hh b/src/include/simeng/pipeline/FetchUnit.hh index a8e44db657..0c617a5547 100644 --- a/src/include/simeng/pipeline/FetchUnit.hh +++ b/src/include/simeng/pipeline/FetchUnit.hh @@ -117,6 +117,13 @@ class FetchUnit { /** The amount of data currently in the fetch buffer. */ uint16_t bufferedBytes_ = 0; + + /** Let the following PipelineFetchUnitTest derived classes be a friend of + * this class to allow proper testing of 'tick' function. */ + friend class PipelineFetchUnitTest_invalidMinBytesAtEndOfBuffer_Test; + friend class PipelineFetchUnitTest_minSizeInstructionAtEndOfBuffer_Test; + friend class PipelineFetchUnitTest_validMinSizeReadsDontComplete_Test; + friend class PipelineFetchUnitTest_invalidMinBytesreadsDontComplete_Test; }; } // namespace pipeline diff --git a/src/lib/arch/aarch64/Architecture.cc b/src/lib/arch/aarch64/Architecture.cc index 3a192a11b2..528d7f9a98 100644 --- a/src/lib/arch/aarch64/Architecture.cc +++ b/src/lib/arch/aarch64/Architecture.cc @@ -239,6 +239,8 @@ ProcessStateChange Architecture::getInitialState() const { uint8_t Architecture::getMaxInstructionSize() const { return 4; } +uint8_t Architecture::getMinInstructionSize() const { return 4; } + void Architecture::updateSystemTimerRegisters(RegisterFileSet* regFile, const uint64_t iterations) const { // Update the Processor Cycle Counter to total cycles completed. diff --git a/src/lib/arch/riscv/Architecture.cc b/src/lib/arch/riscv/Architecture.cc index 6f66382b97..3cb17ae2c8 100644 --- a/src/lib/arch/riscv/Architecture.cc +++ b/src/lib/arch/riscv/Architecture.cc @@ -17,7 +17,25 @@ Architecture::Architecture(kernel::Linux& kernel, ryml::ConstNodeRef config) // TODO set fcsr accordingly when Zicsr extension supported fesetround(FE_TONEAREST); - cs_err n = cs_open(CS_ARCH_RISCV, CS_MODE_RISCV64, &capstoneHandle_); + cs_err n; + + // Check whether compressed instructions in use. Initialise variables and + // Capstone accordingly + if (config["Core"]["Compressed"].as()) { + addressAlignmentMask_ = constantsPool::addressAlignMaskCompressed; + minInsnLength_ = constantsPool::minInstWidthBytesCompressed; + + n = cs_open(CS_ARCH_RISCV, + static_cast(CS_MODE_RISCV64 | CS_MODE_RISCVC), + &capstoneHandle_); + } else { + addressAlignmentMask_ = constantsPool::addressAlignMask; + minInsnLength_ = constantsPool::minInstWidthBytes; + + n = cs_open(CS_ARCH_RISCV, static_cast(CS_MODE_RISCV64), + &capstoneHandle_); + } + if (n != CS_ERR_OK) { std::cerr << "[SimEng:Architecture] Could not create capstone handle due " "to error " @@ -141,7 +159,8 @@ uint8_t Architecture::predecode(const void* ptr, uint16_t bytesAvailable, uint64_t instructionAddress, MacroOp& output) const { // Check that instruction address is 4-byte aligned as required by RISC-V - if (instructionAddress & 0x3) { + // 2-byte when Compressed extension is supported + if (instructionAddress & addressAlignmentMask_) { // Consume 1-byte and raise a misaligned PC exception auto metadata = InstructionMetadata((uint8_t*)ptr, 1); metadataCache_.emplace_front(metadata); @@ -154,31 +173,65 @@ uint8_t Architecture::predecode(const void* ptr, uint16_t bytesAvailable, return 1; } - assert(bytesAvailable >= 4 && - "Fewer than 4 bytes supplied to RISC-V decoder"); - - // Dereference the instruction pointer to obtain the instruction word - uint32_t insn; - memcpy(&insn, ptr, 4); + assert(bytesAvailable >= minInsnLength_ && + "Fewer than bytes limit supplied to RISC-V decoder"); + + // Get the first byte + uint8_t firstByte = ((uint8_t*)ptr)[0]; + + uint32_t insnEncoding = 0; + size_t insnSize = 4; + + // Predecode bytes to determine whether we have a compressed instruction. + // This will allow continuation if a compressed instruction is in the last 2 + // bytes of a fetch block, but will request more data if only half of a + // non-compressed instruction is present + + // Check the 2 least significant bits as these determine instruction length + if ((firstByte & 0b11) != 0b11) { + // 2 byte - compressed + // Only use relevant bytes + // Dereference the instruction pointer to obtain the instruction word + memcpy(&insnEncoding, ptr, 2); + insnSize = 2; + } else { + // 4 byte + if (bytesAvailable < 4) { + // Not enough bytes available, bail + return 0; + } + // Dereference the instruction pointer to obtain the instruction word + memcpy(&insnEncoding, ptr, 4); + } // Try to find the decoding in the decode cache - auto iter = decodeCache_.find(insn); + auto iter = decodeCache_.find(insnEncoding); if (iter == decodeCache_.end()) { // No decoding present. Generate a fresh decoding, and add to cache - cs_insn rawInsn; + // Calloc memory to ensure rawInsn is initialised with zeros. Errors can + // occur otherwise as Capstone doesn't update variables for invalid + // instructions + cs_insn* rawInsnPointer = (cs_insn*)calloc(1, sizeof(cs_insn)); + cs_insn rawInsn = *rawInsnPointer; + assert(rawInsn.size == 0 && "rawInsn not initialised correctly"); + cs_detail rawDetail; rawInsn.detail = &rawDetail; + // Size requires initialisation in case of capstone failure which won't + // update this value + rawInsn.size = insnSize; - size_t size = 4; uint64_t address = 0; const uint8_t* encoding = reinterpret_cast(ptr); - bool success = - cs_disasm_iter(capstoneHandle_, &encoding, &size, &address, &rawInsn); + bool success = cs_disasm_iter(capstoneHandle_, &encoding, &insnSize, + &address, &rawInsn); + + auto metadata = success ? InstructionMetadata(rawInsn) + : InstructionMetadata(encoding, rawInsn.size); - auto metadata = - success ? InstructionMetadata(rawInsn) : InstructionMetadata(encoding); + free(rawInsnPointer); // Cache the metadata metadataCache_.push_front(metadata); @@ -187,10 +240,16 @@ uint8_t Architecture::predecode(const void* ptr, uint16_t bytesAvailable, Instruction newInsn(*this, metadataCache_.front()); // Set execution information for this instruction newInsn.setExecutionInfo(getExecutionInfo(newInsn)); + // Cache the instruction - iter = decodeCache_.insert({insn, newInsn}).first; + iter = decodeCache_.insert({insnEncoding, newInsn}).first; } + assert(((insnEncoding & 0b11) != 0b11 + ? iter->second.getMetadata().getInsnLength() == 2 + : iter->second.getMetadata().getInsnLength() == 4) && + "Predicted number of bytes don't match disassembled number of bytes"); + output.resize(1); auto& uop = output[0]; @@ -199,7 +258,7 @@ uint8_t Architecture::predecode(const void* ptr, uint16_t bytesAvailable, uop->setInstructionAddress(instructionAddress); - return 4; + return iter->second.getMetadata().getInsnLength(); } int32_t Architecture::getSystemRegisterTag(uint16_t reg) const { @@ -231,6 +290,8 @@ ProcessStateChange Architecture::getInitialState() const { uint8_t Architecture::getMaxInstructionSize() const { return 4; } +uint8_t Architecture::getMinInstructionSize() const { return minInsnLength_; } + void Architecture::updateSystemTimerRegisters(RegisterFileSet* regFile, const uint64_t iterations) const { regFile->set(cycleSystemReg_, iterations); diff --git a/src/lib/arch/riscv/ExceptionHandler.cc b/src/lib/arch/riscv/ExceptionHandler.cc index 2ea05d3914..d12e2bc86d 100644 --- a/src/lib/arch/riscv/ExceptionHandler.cc +++ b/src/lib/arch/riscv/ExceptionHandler.cc @@ -909,9 +909,10 @@ void ExceptionHandler::printException(const Instruction& insn) const { << insn.getInstructionAddress() << ": "; auto& metadata = insn.getMetadata(); - for (uint8_t byte : metadata.encoding) { + for (uint8_t byteIndex = 0; byteIndex < metadata.getInsnLength(); + byteIndex++) { std::cout << std::setfill('0') << std::setw(2) - << static_cast(byte) << " "; + << static_cast(metadata.encoding[byteIndex]) << " "; } std::cout << std::dec << " "; if (exception == InstructionException::EncodingUnallocated) { diff --git a/src/lib/arch/riscv/InstructionMetadata.cc b/src/lib/arch/riscv/InstructionMetadata.cc index c3e85579f7..bb98656d4b 100644 --- a/src/lib/arch/riscv/InstructionMetadata.cc +++ b/src/lib/arch/riscv/InstructionMetadata.cc @@ -15,7 +15,12 @@ InstructionMetadata::InstructionMetadata(const cs_insn& insn) implicitSourceCount(insn.detail->regs_read_count), implicitDestinationCount(insn.detail->regs_write_count), operandCount(insn.detail->riscv.op_count) { - std::memcpy(encoding, insn.bytes, sizeof(encoding)); + // Populate 'encoding' field with correct bytes dependent on whether this is a + // compressed instruction + insnLengthBytes_ = insn.size; + std::memset(encoding, 0, 4); + std::memcpy(encoding, insn.bytes, insnLengthBytes_); + // Copy printed output std::strncpy(mnemonic, insn.mnemonic, CS_MNEMONIC_SIZE); operandStr = std::string(insn.op_str); @@ -28,6 +33,7 @@ InstructionMetadata::InstructionMetadata(const cs_insn& insn) std::memcpy(operands, insn.detail->riscv.operands, sizeof(cs_riscv_op) * operandCount); + convertCompressedInstruction(insn); alterPseudoInstructions(insn); } @@ -37,7 +43,8 @@ InstructionMetadata::InstructionMetadata(const uint8_t* invalidEncoding, opcode(Opcode::RISCV_INSTRUCTION_LIST_END), implicitSourceCount(0), implicitDestinationCount(0), - operandCount(0) { + operandCount(0), + insnLengthBytes_(bytes) { assert(bytes <= sizeof(encoding)); std::memcpy(encoding, invalidEncoding, bytes); mnemonic[0] = '\0'; @@ -57,10 +64,10 @@ void InstructionMetadata::alterPseudoInstructions(const cs_insn& insn) { // ADDI _, _, _-> ADDI x0, x0, 0 // reg set to 1 to reflect capstones 1 indexed output operands[0].type = RISCV_OP_REG; - operands[0].reg = 1; + operands[0].reg = RISCV_REG_ZERO; operands[1].type = RISCV_OP_REG; - operands[1].reg = 1; + operands[1].reg = RISCV_REG_ZERO; operands[2].type = RISCV_OP_IMM; operands[2].imm = 0; @@ -138,7 +145,7 @@ void InstructionMetadata::alterPseudoInstructions(const cs_insn& insn) { // sltz Rd, Rs is pseudo of SLT Rd, Rs, x0 // SLT Rd, Rs, _ -> SLT Rd, Rs, x0 operands[2].type = RISCV_OP_REG; - operands[2].reg = 1; + operands[2].reg = RISCV_REG_ZERO; operandCount = 3; } else if (operandCount == 2 && strcmp(mnemonic, "sgtz") == 0) { @@ -153,10 +160,10 @@ void InstructionMetadata::alterPseudoInstructions(const cs_insn& insn) { // ret is pseudo of JALR x0, x1, 0 // JALR _, _, _ -> JALR x0, x1, 0 operands[0].type = RISCV_OP_REG; - operands[0].reg = 1; + operands[0].reg = RISCV_REG_ZERO; operands[1].type = RISCV_OP_REG; - operands[1].reg = 2; + operands[1].reg = RISCV_REG_RA; operands[2].type = RISCV_OP_IMM; operands[2].imm = 0; @@ -166,7 +173,7 @@ void InstructionMetadata::alterPseudoInstructions(const cs_insn& insn) { // jr Rs is pseudo of JALR x0, Rs, 0 // JALR Rs, _, _ -> JALR x0, Rs, 0 operands[0].type = RISCV_OP_REG; - operands[0].reg = 1; + operands[0].reg = RISCV_REG_ZERO; operands[1] = insn.detail->riscv.operands[0]; @@ -178,7 +185,7 @@ void InstructionMetadata::alterPseudoInstructions(const cs_insn& insn) { // jalr Rs is pseudo of JALR x1, Rs, 0 // JALR Rs, _, _ -> JALR x1, Rs, 0 operands[0].type = RISCV_OP_REG; - operands[0].reg = 2; + operands[0].reg = RISCV_REG_RA; operands[1] = insn.detail->riscv.operands[0]; @@ -194,7 +201,7 @@ void InstructionMetadata::alterPseudoInstructions(const cs_insn& insn) { // jal offset is pseudo of JAL x1, offset // JAL offset, _ -> JAL x1, offset operands[0].type = RISCV_OP_REG; - operands[0].reg = 2; + operands[0].reg = RISCV_REG_RA; operands[1].type = RISCV_OP_IMM; operands[1].imm = insn.detail->riscv.operands[0].imm; @@ -204,7 +211,7 @@ void InstructionMetadata::alterPseudoInstructions(const cs_insn& insn) { // j offset is pseudo of JAL x0, offset // JAL offset, _ -> JAL x0, offset operands[0].type = RISCV_OP_REG; - operands[0].reg = 1; + operands[0].reg = RISCV_REG_ZERO; operands[1].type = RISCV_OP_IMM; operands[1].imm = insn.detail->riscv.operands[0].imm; @@ -260,10 +267,10 @@ void InstructionMetadata::alterPseudoInstructions(const cs_insn& insn) { // flags) CSRRS Rs, _, _ -> CSRRS Rs, fflags, zero operands[1].type = RISCV_OP_IMM; // TODO needs to become reg when Capstone updated - operands[1].reg = RISCV_SYSREG_FFLAGS; // fflags address + operands[1].imm = RISCV_SYSREG_FFLAGS; // fflags address operands[2].type = RISCV_OP_REG; - operands[2].reg = 1; + operands[2].reg = RISCV_REG_ZERO; operandCount = 3; } else if (strcmp(mnemonic, "rdinstret") == 0) { @@ -283,10 +290,10 @@ void InstructionMetadata::alterPseudoInstructions(const cs_insn& insn) { // CSRRS Rs, _, _ -> CSRRS Rs, frm, zero operands[1].type = RISCV_OP_IMM; // TODO needs to become reg when Capstone updated - operands[1].reg = RISCV_SYSREG_FRM; // frm address + operands[1].imm = RISCV_SYSREG_FRM; // frm address operands[2].type = RISCV_OP_REG; - operands[2].reg = 1; + operands[2].reg = RISCV_REG_ZERO; operandCount = 3; } @@ -300,11 +307,11 @@ void InstructionMetadata::alterPseudoInstructions(const cs_insn& insn) { operands[2] = operands[0]; operands[0].type = RISCV_OP_REG; - operands[0].reg = 1; + operands[0].reg = RISCV_REG_ZERO; operands[1].type = RISCV_OP_IMM; // TODO needs to become reg when Capstone updated - operands[1].reg = RISCV_SYSREG_FFLAGS; // fflags address + operands[1].imm = RISCV_SYSREG_FFLAGS; // fflags address operandCount = 3; } else if (operandCount == 2 && strcmp(mnemonic, "fsflags") == 0) { @@ -315,7 +322,7 @@ void InstructionMetadata::alterPseudoInstructions(const cs_insn& insn) { operands[1].type = RISCV_OP_IMM; // TODO needs to become reg when Capstone updated - operands[1].reg = RISCV_SYSREG_FFLAGS; // fflags address + operands[1].imm = RISCV_SYSREG_FFLAGS; // fflags address operandCount = 3; } else if (strcmp(mnemonic, "csrw") == 0) { @@ -331,11 +338,11 @@ void InstructionMetadata::alterPseudoInstructions(const cs_insn& insn) { operands[2] = operands[0]; operands[0].type = RISCV_OP_REG; - operands[0].reg = 1; + operands[0].reg = RISCV_REG_ZERO; operands[1].type = RISCV_OP_IMM; // TODO needs to become reg when Capstone updated - operands[1].reg = RISCV_SYSREG_FRM; // frm address + operands[1].imm = RISCV_SYSREG_FRM; // frm address operandCount = 3; } else if (operandCount == 2 && strcmp(mnemonic, "fsrm") == 0) { @@ -344,7 +351,7 @@ void InstructionMetadata::alterPseudoInstructions(const cs_insn& insn) { operands[2] = operands[1]; operands[1].type = RISCV_OP_IMM; - operands[1].reg = RISCV_SYSREG_FRM; + operands[1].imm = RISCV_SYSREG_FRM; operandCount = 3; } @@ -426,7 +433,7 @@ void InstructionMetadata::includeZeroRegisterPosOne() { operands[2] = operands[1]; operands[1].type = RISCV_OP_REG; - operands[1].reg = 1; + operands[1].reg = RISCV_REG_ZERO; operandCount = 3; } @@ -437,11 +444,520 @@ void InstructionMetadata::includeZeroRegisterPosZero() { operands[1] = operands[0]; operands[0].type = RISCV_OP_REG; - operands[0].reg = 1; + operands[0].reg = RISCV_REG_ZERO; + + operandCount = 3; +} + +void InstructionMetadata::duplicateFirstOp() { + // Given register sequence {Op_a, Op_b, _} return {Op_a, Op_a, Op_b} + operands[2] = operands[1]; + operands[1] = operands[0]; operandCount = 3; } +void InstructionMetadata::createMemOpPosOne() { + // Given register sequence {Op_a, imm, reg} return {Op_a, mem, _} + assert(operands[1].type == RISCV_OP_IMM && + "Incorrect operand type when creating memory operand"); + assert(operands[2].type == RISCV_OP_REG && + "Incorrect operand type when creating memory operand"); + + cs_riscv_op temp; + temp.type = RISCV_OP_MEM; + temp.mem.base = operands[2].reg; + temp.mem.disp = operands[1].imm; + + operands[1] = temp; + + operandCount = 2; +} + +void InstructionMetadata::convertCompressedInstruction(const cs_insn& insn) { + if (insnLengthBytes_ != 2) { + return; + } + + switch (insn.opcode) { + case Opcode::RISCV_C_JR: + // jalr x0, 0(rs1) + // C.JR rs1, _, _ -> JALR x0, rs1, 0 + + // rs1=zero is reserved + assert((operands[0].type == RISCV_OP_REG && + operands[0].reg != RISCV_REG_ZERO) && + "C.JR has rs1=x0 which is reserved"); + + opcode = Opcode::RISCV_JALR; + + operands[0].type = RISCV_OP_REG; + operands[0].reg = RISCV_REG_ZERO; + + operands[1] = insn.detail->riscv.operands[0]; + + operands[2].type = RISCV_OP_IMM; + operands[2].imm = 0; + + operandCount = 3; + + break; + case Opcode::RISCV_C_MV: + // add rd, x0, rs2 + // rs2 == zero and rd == zero are hints + // C.MV rd, rs2, _ -> ADD rd, zero, rs2 + + // rs2 = zero corresponds to C.JR + assert((operands[1].type == RISCV_OP_REG && + operands[1].reg != RISCV_REG_ZERO) && + "C.MV has rs2=x0 which is invalid"); + + opcode = Opcode::RISCV_ADD; + + includeZeroRegisterPosOne(); + + break; + case Opcode::RISCV_C_LDSP: { + // TODO valid for RV64 only. Make this check once RV32 implemented + // ld rd, offset[8:3](x2) + // offset is immediate scaled by 8. Capstone does scaling for us + + // rd = zero is reserved + assert((operands[0].type == RISCV_OP_REG && + operands[0].reg != RISCV_REG_ZERO) && + "C.LDSP has rd=x0 which is reserved"); + + opcode = Opcode::RISCV_LD; + + // Create operand formatted like LD instruction + createMemOpPosOne(); + + break; + } + case Opcode::RISCV_C_ADDI4SPN: + // addi rd ′ , x2, nzuimm[9:2] + + // nzuimm = zero is reserved + assert((operands[2].type == RISCV_OP_IMM && operands[2].imm != 0) && + "C.ADDI4SPN has nzuimm=0 which is reserved"); + + opcode = Opcode::RISCV_ADDI; + // All operands correct + break; + case Opcode::RISCV_C_LI: + // addi rd, x0, imm[5:0] + // C.LI rd, imm, _ -> addi rd, zero, imm + + // rd = zero encodes hints + assert((operands[0].type == RISCV_OP_REG && + operands[0].reg != RISCV_REG_ZERO) && + "C.LI has rd=x0 which is invalid"); + + opcode = Opcode::RISCV_ADDI; + + includeZeroRegisterPosOne(); + + break; + case Opcode::RISCV_C_ADDI16SP: + // Opcode shared with C.LUI but has Rd = x2 + // addi x2, x2, nzimm[9:4] + // C.ADDI16SP sp, imm, _ -> addi sp, sp, imm + + // nzimm = zero is reserved + assert((operands[1].type == RISCV_OP_IMM && operands[1].imm != 0) && + "C.ADDI16SP has nzimm=0 which is reserved"); + + opcode = Opcode::RISCV_ADDI; + + duplicateFirstOp(); + + break; + case Opcode::RISCV_C_SLLI: + // slli rd, rd, shamt[5:0] + // + // "For RV32C, shamt[5] must be zero; the code points with shamt[5]=1 are + // reserved for custom extensions. For RV32C and RV64C, the shift amount + // must be non-zero; the code points with shamt=0 are HINTs. For all base + // ISAs, the code points with rd=x0 are HINTs, except those with + // shamt[5]=1 in RV32C." - Spec page 107 + // + // C.SLLI rd, shamt, _ -> slli rd, rd, shamt + + // shamt = zero is reserved for hints + assert((operands[1].type == RISCV_OP_IMM && operands[1].imm != 0) && + "C.SLLI has shamt=0 which is reserved for hints"); + + // rd = zero encodes hints + assert((operands[0].type == RISCV_OP_REG && + operands[0].reg != RISCV_REG_ZERO) && + "C.SLLI has rd=x0 which is reserved for hints"); + + opcode = Opcode::RISCV_SLLI; + + duplicateFirstOp(); + + break; + case Opcode::RISCV_C_SDSP: { + // TODO rv64 ONLY, make check for this once RV32 implemented + // sd rs2, offset[8:3](x2) + + opcode = Opcode::RISCV_SD; + + // Create operand formatted like SD instruction + createMemOpPosOne(); + + break; + } + case Opcode::RISCV_C_SWSP: { + // sw rs2, offset[7:2](x2) + opcode = Opcode::RISCV_SW; + + createMemOpPosOne(); + + break; + } + case Opcode::RISCV_C_ADD: + // add rd, rd, rs2 + // + // "code points with rs2=x0 correspond + // to the C.JALR and C.EBREAK + // instructions. The code points with + // rs2̸=x0 and rd=x0 are HINTs." - Spec page 108 + // + // C.ADD rd, rs2, _ -> add rd, rd, rs2 + + // rs2 = zero corresponds to C.JALR and C.EBREAK + assert((operands[1].type == RISCV_OP_REG && + operands[1].reg != RISCV_REG_ZERO) && + "C.ADD has rs2=x0 which is invalid"); + + // rs2 = zero AND rd = zero are reserved for hints + assert((operands[0].type == RISCV_OP_REG && + operands[0].reg != RISCV_REG_ZERO && + operands[1].type == RISCV_OP_REG && + operands[1].reg != RISCV_REG_ZERO) && + "C.ADD has rs2=x0 and rd=x0 which is reserved for hints"); + + opcode = Opcode::RISCV_ADD; + + duplicateFirstOp(); + + break; + case Opcode::RISCV_C_LD: { + // TODO rv64 ONLY, make check for this once RV32 implemented + // ld rd ′ , offset[7:3](rs1 ′) + + opcode = Opcode::RISCV_LD; + + // Create operand formatted like LD instruction + createMemOpPosOne(); + + break; + } + case Opcode::RISCV_C_ADDI: { + // addi rd, rd, nzimm[5:0] + // C.ADDI rd, imm, _ -> addi rd, rd, imm + + // rd = zero encodes C.NOP + assert((operands[0].type == RISCV_OP_REG && + operands[0].reg != RISCV_REG_ZERO) && + "C.ADDI has rd=x0 which is invalid"); + + // nzimm = zero is reserved for hints + assert((operands[1].type == RISCV_OP_IMM && operands[1].imm != 0) && + "C.ADDI has nzimm=0 which is reserved for hints"); + + opcode = Opcode::RISCV_ADDI; + + duplicateFirstOp(); + + break; + } + case Opcode::RISCV_C_BNEZ: + // bne rs1 ′ , x0, offset[8:1] + // C.BNEZ rs1, imm, _ -> bne rs1, zero, imm + opcode = Opcode::RISCV_BNE; + + includeZeroRegisterPosOne(); + + break; + case Opcode::RISCV_C_SD: { + // TODO rv64 ONLY, make check for this once RV32 implemented + // sd rs2 ′ , offset[7:3](rs1 ′) + + opcode = Opcode::RISCV_SD; + // Create operand formatted like SD instruction + createMemOpPosOne(); + + break; + } + case Opcode::RISCV_C_BEQZ: + // beq rs1 ′ , x0, offset[8:1] + // C.BEQZ rs1, imm, _ -> beq rs1, zero, imm + opcode = Opcode::RISCV_BEQ; + + includeZeroRegisterPosOne(); + + break; + case Opcode::RISCV_C_ANDI: + // andi rd ′, rd ′ , imm[5:0] + // C.ANDI rd, imm, _ -> andi rd, rd, imm + opcode = Opcode::RISCV_ANDI; + + duplicateFirstOp(); + + break; + case Opcode::RISCV_C_LUI: + // lui rd, nzimm[17:12] + + // nzimm = zero is reserved + assert((operands[1].type == RISCV_OP_IMM && operands[1].imm != 0) && + "C.LUI has nzimm=0 which is reserved"); + + // rd = zero is reserved for hints + assert((operands[0].type == RISCV_OP_REG && + operands[0].reg != RISCV_REG_ZERO) && + "C.LUI has rd=x0 which is reserved for hints"); + + // rd = x2 encodes C.ADDI16SP + assert((operands[0].type == RISCV_OP_REG && + operands[0].reg != RISCV_REG_SP) && + "C.LUI has rd=x2 which is invalid"); + + opcode = Opcode::RISCV_LUI; + // All operands correct + break; + case Opcode::RISCV_C_LWSP: { + // lw rd, offset[7:2](x2) + + // rd = zero is reserved + assert((operands[0].type == RISCV_OP_REG && + operands[0].reg != RISCV_REG_ZERO) && + "C.LWSP has rd=x0 which is reserved"); + + opcode = Opcode::RISCV_LW; + + createMemOpPosOne(); + + break; + } + case Opcode::RISCV_C_FLDSP: + // TODO RV32DC/RV64DC-only once RV32 implemented + // fld rd, offset[8:3](x2) + opcode = Opcode::RISCV_FLD; + + createMemOpPosOne(); + + break; + case Opcode::RISCV_C_SW: { + // sw rs2 ′, offset[6:2](rs1 ′) + + opcode = Opcode::RISCV_SW; + + createMemOpPosOne(); + + break; + } + case Opcode::RISCV_C_J: + // jal x0, offset[11:1] + // C.J imm, _ -> jal zero, imm + opcode = Opcode::RISCV_JAL; + + operands[1] = operands[0]; + + operands[0].type = RISCV_OP_REG; + operands[0].reg = RISCV_REG_ZERO; + + operandCount = 2; + + break; + case Opcode::RISCV_C_ADDIW: + // TODO rv64 ONLY, make check for this once RV32 implemented + // addiw rd, rd, imm[5:0] + // C.ADDIW rd, imm, _ -> addiw rd, rd, imm + + // "The immediate can be zero for C.ADDIW, where this corresponds to + // [pseudoinstruction] sext.w rd" - Spec page 106 + // rd = zero is reserved + assert((operands[0].type == RISCV_OP_REG && + operands[0].reg != RISCV_REG_ZERO) && + "C.ADDIW has rd=x0 which is reserved"); + + opcode = Opcode::RISCV_ADDIW; + + duplicateFirstOp(); + + break; + case Opcode::RISCV_C_SUB: + // sub rd ′ , rd ′ , rs2 ′ + // C.SUB rd, rs2, -> sub rd, rd, rs2 + opcode = Opcode::RISCV_SUB; + + duplicateFirstOp(); + + break; + case Opcode::RISCV_C_LW: + // lw rd ′ , offset[6:2](rs1 ′ ) + + opcode = Opcode::RISCV_LW; + + createMemOpPosOne(); + + break; + case Opcode::RISCV_C_SRLI: + // srli rd ′ , rd ′ , shamt[5:0] + // C.SRLI rd, imm, _ -> srli rd, rd, imm + + // shamt = zero is reserved for hints + assert((operands[1].type == RISCV_OP_IMM && operands[1].imm != 0) && + "C.SRLI has shamt=0 which is reserved for hints"); + + opcode = Opcode::RISCV_SRLI; + + duplicateFirstOp(); + + break; + case Opcode::RISCV_C_ADDW: + // TODO rv64 ONLY, make check for this once RV32 implemented + // addw rd ′ , rd ′ , rs2 ′ + // C.ADDW rd, rs2, _ -> addw rd, rd, rs2 + opcode = Opcode::RISCV_ADDW; + + duplicateFirstOp(); + + break; + case Opcode::RISCV_C_AND: + // and rd ′ , rd ′ , rs2 ′ + // C.AND rd, rs2, _ -> and rd, rd, rs2 + opcode = Opcode::RISCV_AND; + + duplicateFirstOp(); + + break; + case Opcode::RISCV_C_OR: + // or rd ′ , rd ′ , rs2 ′ + // C.OR rd, rs2, _ -> or rd, rd, rs2 + + opcode = Opcode::RISCV_OR; + + duplicateFirstOp(); + + break; + case Opcode::RISCV_C_JALR: + // jalr x1, 0(rs1) + // C.JALR rs1, _, _ -> jalr x1, rs1, 0 + + // rs1=zero corresponds to C.EBREAK instruction + assert((operands[0].type == RISCV_OP_REG && + operands[0].reg != RISCV_REG_ZERO) && + "C.JALR has rs1=x0 which is invalid"); + + opcode = Opcode::RISCV_JALR; + + operands[1] = operands[0]; + + operands[0].reg = RISCV_REG_RA; + + operands[2].type = RISCV_OP_IMM; + operands[2].imm = 0; + + operandCount = 3; + + break; + case Opcode::RISCV_C_XOR: + // xor rd ′ , rd ′ , rs2 ′ + // C.XOR rd, rs2, _ -> xor rd, rd, rs2 + + opcode = Opcode::RISCV_XOR; + + duplicateFirstOp(); + + break; + case Opcode::RISCV_C_SRAI: + // srai rd ′ , rd ′ , shamt[5:0] + // C.SRAI rd, imm, _ -> srai rd, rd, imm + + // shamt = zero is reserved for hints + assert((operands[1].type == RISCV_OP_IMM && operands[1].imm != 0) && + "C.SRAI has shamt=0 which is reserved for hints"); + + opcode = Opcode::RISCV_SRAI; + + duplicateFirstOp(); + + break; + case Opcode::RISCV_C_FSD: + // TODO rv64dc ONLY, make check for this once RV32 implemented + // fsd rs2 ′, offset[7:3](rs1 ′) + + opcode = Opcode::RISCV_FSD; + + createMemOpPosOne(); + + break; + case Opcode::RISCV_C_FLD: + // TODO rv64dc ONLY, make check for this once RV32 implemented + // fld rd ′, offset[7:3](rs1 ′) + + opcode = Opcode::RISCV_FLD; + + createMemOpPosOne(); + + break; + case Opcode::RISCV_C_FSDSP: + // TODO rv64dc ONLY, make check for this once RV32 implemented + // fsd rs2, offset[8:3](x2) + + opcode = Opcode::RISCV_FSD; + + createMemOpPosOne(); + + break; + case Opcode::RISCV_C_SUBW: + // TODO rv64 ONLY, make check for this once RV32 implemented + // subw rd ′ , rd ′ , rs2 ′ + // C.SUBW rd, rs2, _ -> subw rd, rd, rs2 + + opcode = Opcode::RISCV_SUBW; + + duplicateFirstOp(); + + break; + case Opcode::RISCV_C_NOP: + // nop + // C.NOP _, _, _-> addi x0, x0, 0 + + // TODO imm != zero is reserved for hints. Capstone doesn't give this + // value so can't be checked + + opcode = Opcode::RISCV_ADDI; + + // Duplicate implementation of nop pseudoinstruction + operands[0].type = RISCV_OP_REG; + operands[0].reg = RISCV_REG_ZERO; + + operands[1].type = RISCV_OP_REG; + operands[1].reg = RISCV_REG_ZERO; + + operands[2].type = RISCV_OP_IMM; + operands[2].imm = 0; + + operandCount = 3; + + break; + case Opcode::RISCV_C_EBREAK: + // ebreak + + opcode = Opcode::RISCV_EBREAK; + + break; + default: + // Unimplemented compressed instruction, raise exception + aliasNYI(); + break; + } +} + } // namespace riscv } // namespace arch } // namespace simeng \ No newline at end of file diff --git a/src/lib/arch/riscv/InstructionMetadata.hh b/src/lib/arch/riscv/InstructionMetadata.hh index 2b980a2e88..9a641353f9 100644 --- a/src/lib/arch/riscv/InstructionMetadata.hh +++ b/src/lib/arch/riscv/InstructionMetadata.hh @@ -30,11 +30,14 @@ struct InstructionMetadata { return metadataException_; } - /* Returns a bool stating whether an exception has been encountered */ + /* Returns a bool stating whether an exception has been encountered. */ bool getMetadataExceptionEncountered() const { return metadataExceptionEncountered_; } + /* Returns the length of the instruction in bytes. */ + uint8_t getInsnLength() const { return insnLengthBytes_; } + /** The maximum operand string length as defined in Capstone */ static const size_t MAX_OPERAND_STR_LENGTH = sizeof(cs_insn::op_str) / sizeof(char); @@ -90,7 +93,11 @@ struct InstructionMetadata { * instruction. */ void alterPseudoInstructions(const cs_insn& insn); - /** Flag the instruction as NYI due to a detected unsupported alias. */ + /** Detect compressed instructions and update metadata to match the + * non-compressed instruction expansion. */ + void convertCompressedInstruction(const cs_insn& insn); + + /** Flag the instruction as aliasNYI due to a detected unsupported alias. */ void aliasNYI(); /** RISC-V helper function @@ -101,11 +108,23 @@ struct InstructionMetadata { * Use register zero as operands[0] and immediate value as operands[2] */ void includeZeroRegisterPosZero(); + /** RISC-V helper function + * Duplicate operands[0] and move operands[1] to operands[2] */ + void duplicateFirstOp(); + + /** RISC-V helper function + * Combine operands[1] and operands[2] which are of type imm and reg + * respectively into a single mem type operand */ + void createMemOpPosOne(); + /** The current exception state of this instruction. */ InstructionException metadataException_ = InstructionException::None; /** Whether an exception has been encountered. */ bool metadataExceptionEncountered_ = false; + + /** The length of the instruction encoding in bytes. */ + uint8_t insnLengthBytes_; }; } // namespace riscv diff --git a/src/lib/arch/riscv/Instruction_address.cc b/src/lib/arch/riscv/Instruction_address.cc index faa2e75098..3a9fce30ff 100644 --- a/src/lib/arch/riscv/Instruction_address.cc +++ b/src/lib/arch/riscv/Instruction_address.cc @@ -16,11 +16,37 @@ span Instruction::generateAddresses() { if (isInstruction(InsnType::isLoad) && isInstruction(InsnType::isStore) && isInstruction(InsnType::isAtomic)) { // Atomics + // Metadata operands[2] corresponds to instruction sourceRegValues[1] + assert(metadata_.operands[2].type == RISCV_OP_REG && + "metadata_ operand not of correct type during RISC-V address " + "generation"); + address = sourceValues_[1].get(); + } else if (isInstruction(InsnType::isLoad) && + isInstruction(InsnType::isAtomic)) { + // Load reserved + // Metadata operands[1] corresponds to instruction sourceRegValues[0] + assert(metadata_.operands[1].type == RISCV_OP_REG && + "metadata_ operand not of correct type during RISC-V address " + "generation"); + address = sourceValues_[0].get(); + } else if (isInstruction(InsnType::isStore) && + isInstruction(InsnType::isAtomic)) { + // Store conditional + assert(metadata_.operands[2].type == RISCV_OP_REG && + "metadata_ operand not of correct type during RISC-V address " + "generation"); address = sourceValues_[1].get(); } else if (isInstruction(InsnType::isLoad)) { - address = sourceValues_[0].get() + metadata_.operands[1].mem.disp; + assert(metadata_.operands[1].type == RISCV_OP_MEM && + "metadata_ operand not of correct type during RISC-V address " + "generation"); + address = sourceValues_[0].get() + sourceImm_; } else { - address = sourceValues_[1].get() + metadata_.operands[1].mem.disp; + assert((metadata_.operands[1].type == RISCV_OP_MEM) && + "metadata_ operand not of correct type during RISC-V address " + "generation"); + + address = sourceValues_[1].get() + sourceImm_; } // Atomics diff --git a/src/lib/arch/riscv/Instruction_decode.cc b/src/lib/arch/riscv/Instruction_decode.cc index e11268c1e5..e8145d4c11 100644 --- a/src/lib/arch/riscv/Instruction_decode.cc +++ b/src/lib/arch/riscv/Instruction_decode.cc @@ -132,7 +132,7 @@ void Instruction::decode() { setInstructionType(InsnType::isAtomic); } - // Extract explicit register accesses, ignore immediates until execute + // Extract explicit register accesses and immediates for (size_t i = 0; i < metadata_.operandCount; i++) { const auto& op = metadata_.operands[i]; @@ -159,57 +159,67 @@ void Instruction::decode() { sourceRegisterCount_++; } else { + /** + * Register writes to x0 are discarded so no destination register is + * set. + * + * While the execution stage may still write to the results array, + * when Instruction::getResults and + * Instruction::getDestinationRegisters are called during writeback, + * zero sized spans are returned (determined by the value of + * destinationRegisterCount). This in turn means no register update is + * performed. + * + * TODO this will break if there are more than 2 destination registers + * with one being the zero register e.g. if an instruction implicitly + * writes to a system register. The current implementation could mean + * that the second result is discarded + * + */ if (csRegToRegister(op.reg) != RegisterType::ZERO_REGISTER) { destinationRegisters_[destinationRegisterCount_] = csRegToRegister(op.reg); destinationRegisterCount_++; - } else { - /** - * Register writes to x0 are discarded so no destination register is - * set. - * - * While the execution stage may still write to the results array, - * when Instruction::getResults and - * Instruction::getDestinationRegisters are called during writeback, - * zero sized spans are returned (determined by the value of - * destinationRegisterCount). This in turn means no register update is - * performed. - * - * TODO this will break if there are more than 2 destination registers - * with one being the zero register e.g. if an instruction implicitly - * writes to a system register. The current implementation could mean - * that the second result is discarded - * - */ } } - } + } else if (i > 0) { + // First operand is never of MEM or IMM type, every register operand after + // the first is a source register + if (op.type == RISCV_OP_REG) { + // Second or third register operand + sourceRegisters_[sourceRegisterCount_] = csRegToRegister(op.reg); - // For all instructions, every register operand after the first is a source - // register - else if (i > 0 && op.type == RISCV_OP_REG) { - // Second or third operand - sourceRegisters_[sourceRegisterCount_] = csRegToRegister(op.reg); + if (sourceRegisters_[sourceRegisterCount_] == + RegisterType::ZERO_REGISTER) { + // Catch zero register references and pre-complete those operands + sourceValues_[sourceRegisterCount_] = RegisterValue(0, 8); + } else { + sourceOperandsPending_++; + } - if (sourceRegisters_[sourceRegisterCount_] == - RegisterType::ZERO_REGISTER) { - // Catch zero register references and pre-complete those operands - sourceValues_[sourceRegisterCount_] = RegisterValue(0, 8); - } else { + sourceRegisterCount_++; + } else if (op.type == RISCV_OP_MEM) { + // Memory operand + // Extract reg number from capstone object + sourceRegisters_[sourceRegisterCount_] = csRegToRegister(op.mem.base); + sourceImm_ = op.mem.disp; + sourceRegisterCount_++; sourceOperandsPending_++; + } else if (op.type == RISCV_OP_IMM) { + // Immediate operand + sourceImm_ = op.imm; + } else { + // Something has gone wrong + assert(false && + "Unexpected register type in non-first " + "operand position"); } - - sourceRegisterCount_++; - } - - // First operand is never MEM type, only check after the first. If register - // contains memory address, extract reg number from capstone object - else if (i > 0 && op.type == RISCV_OP_MEM) { - // Memory operand - sourceRegisters_[sourceRegisterCount_] = csRegToRegister(op.mem.base); - sourceRegisterCount_++; - sourceOperandsPending_++; + } else { + // Something has gone wrong + assert(false && + "Unexpected register type in first " + "operand position"); } } @@ -292,12 +302,12 @@ void Instruction::decode() { case Opcode::RISCV_BGE: case Opcode::RISCV_BGEU: branchType_ = BranchType::Conditional; - knownOffset_ = metadata_.operands[2].imm; + knownOffset_ = sourceImm_; break; case Opcode::RISCV_JAL: case Opcode::RISCV_JALR: branchType_ = BranchType::Unconditional; - knownOffset_ = metadata_.operands[1].imm; + knownOffset_ = sourceImm_; break; } } diff --git a/src/lib/arch/riscv/Instruction_execute.cc b/src/lib/arch/riscv/Instruction_execute.cc index e464d7a471..2b7e7255e2 100644 --- a/src/lib/arch/riscv/Instruction_execute.cc +++ b/src/lib/arch/riscv/Instruction_execute.cc @@ -173,11 +173,11 @@ void Instruction::executionNYI() { void Instruction::execute() { assert(!executed_ && "Attempted to execute an instruction more than once"); - assert( - canExecute() && - "Attempted to execute an instruction before all operands were provided"); + assert(canExecute() && + "Attempted to execute an instruction before all source operands were " + "provided"); - // Implementation of rv64iamfd according to the v. 20191213 unprivileged spec + // Implementation of rv64iamfdc according to the v. 20191213 unprivileged spec executed_ = true; switch (metadata_.opcode) { @@ -235,8 +235,7 @@ void Instruction::execute() { } case Opcode::RISCV_SLLI: { // SLLI rd,rs1,shamt const int64_t rs1 = sourceValues_[0].get(); - const int64_t shamt = - metadata_.operands[2].imm & 63; // Only use lowest 6 bits + const int64_t shamt = sourceImm_ & 63; // Only use lowest 6 bits int64_t out = static_cast(rs1 << shamt); results_[0] = RegisterValue(out, 8); break; @@ -251,8 +250,7 @@ void Instruction::execute() { } case Opcode::RISCV_SLLIW: { // SLLIW rd,rs1,shamt const int32_t rs1 = sourceValues_[0].get(); - const int32_t shamt = - metadata_.operands[2].imm & 63; // Only use lowest 6 bits + const int32_t shamt = sourceImm_ & 63; // Only use lowest 6 bits uint64_t out = signExtendW(static_cast(rs1 << shamt)); results_[0] = RegisterValue(out, 8); break; @@ -267,8 +265,7 @@ void Instruction::execute() { } case Opcode::RISCV_SRLI: { // SRLI rd,rs1,shamt const uint64_t rs1 = sourceValues_[0].get(); - const uint64_t shamt = - metadata_.operands[2].imm & 63; // Only use lowest 6 bits + const uint64_t shamt = sourceImm_ & 63; // Only use lowest 6 bits uint64_t out = static_cast(rs1 >> shamt); results_[0] = RegisterValue(out, 8); break; @@ -283,8 +280,7 @@ void Instruction::execute() { } case Opcode::RISCV_SRLIW: { // SRLIW rd,rs1,shamt const uint32_t rs1 = sourceValues_[0].get(); - const uint32_t shamt = - metadata_.operands[2].imm & 63; // Only use lowest 6 bits + const uint32_t shamt = sourceImm_ & 63; // Only use lowest 6 bits uint64_t out = signExtendW(static_cast(rs1 >> shamt)); results_[0] = RegisterValue(out, 8); break; @@ -299,8 +295,7 @@ void Instruction::execute() { } case Opcode::RISCV_SRAI: { // SRAI rd,rs1,shamt const int64_t rs1 = sourceValues_[0].get(); - const int64_t shamt = - metadata_.operands[2].imm & 63; // Only use lowest 6 bits + const int64_t shamt = sourceImm_ & 63; // Only use lowest 6 bits int64_t out = static_cast(rs1 >> shamt); results_[0] = RegisterValue(out, 8); break; @@ -315,8 +310,7 @@ void Instruction::execute() { } case Opcode::RISCV_SRAIW: { // SRAIW rd,rs1,shamt const int32_t rs1 = sourceValues_[0].get(); - const int32_t shamt = - metadata_.operands[2].imm & 63; // Only use lowest 6 bits + const int32_t shamt = sourceImm_ & 63; // Only use lowest 6 bits int64_t out = static_cast(rs1 >> shamt); results_[0] = RegisterValue(out, 8); break; @@ -337,15 +331,13 @@ void Instruction::execute() { } case Opcode::RISCV_ADDI: { // ADDI rd,rs1,imm const uint64_t rs1 = sourceValues_[0].get(); - const uint64_t rs2 = metadata_.operands[2].imm; - uint64_t out = static_cast(rs1 + rs2); + uint64_t out = static_cast(rs1 + sourceImm_); results_[0] = RegisterValue(out, 8); break; } case Opcode::RISCV_ADDIW: { // ADDIW rd,rs1,imm const int32_t rs1 = sourceValues_[0].get(); - const int32_t imm = metadata_.operands[2].imm; - uint64_t out = signExtendW(rs1 + imm); + uint64_t out = signExtendW(rs1 + sourceImm_); results_[0] = RegisterValue(out, 8); break; } @@ -363,16 +355,15 @@ void Instruction::execute() { results_[0] = RegisterValue(out, 8); break; } - case Opcode::RISCV_LUI: { // LUI rd,imm - uint64_t out = signExtendW(metadata_.operands[1].imm - << 12); // Shift into upper 20 bits + case Opcode::RISCV_LUI: { // LUI rd,imm + uint64_t out = signExtendW(sourceImm_ << 12); // Shift into upper 20 bits results_[0] = RegisterValue(out, 8); break; } case Opcode::RISCV_AUIPC: { // AUIPC rd,imm const int64_t pc = instructionAddress_; - const int64_t uimm = signExtendW(metadata_.operands[1].imm - << 12); // Shift into upper 20 bits + const int64_t uimm = + signExtendW(sourceImm_ << 12); // Shift into upper 20 bits uint64_t out = static_cast(pc + uimm); results_[0] = RegisterValue(out, 8); break; @@ -386,8 +377,7 @@ void Instruction::execute() { } case Opcode::RISCV_XORI: { // XORI rd,rs1,imm const uint64_t rs1 = sourceValues_[0].get(); - const uint64_t imm = metadata_.operands[2].imm; - uint64_t out = static_cast(rs1 ^ imm); + uint64_t out = static_cast(rs1 ^ sourceImm_); results_[0] = RegisterValue(out, 8); break; } @@ -400,8 +390,7 @@ void Instruction::execute() { } case Opcode::RISCV_ORI: { // ORI rd,rs1,imm const uint64_t rs1 = sourceValues_[0].get(); - const uint64_t imm = metadata_.operands[2].imm; - uint64_t out = static_cast(rs1 | imm); + uint64_t out = static_cast(rs1 | sourceImm_); results_[0] = RegisterValue(out, 8); break; } @@ -414,8 +403,7 @@ void Instruction::execute() { } case Opcode::RISCV_ANDI: { // ANDI rd,rs1,imm const uint64_t rs1 = sourceValues_[0].get(); - const uint64_t imm = metadata_.operands[2].imm; - uint64_t out = static_cast(rs1 & imm); + uint64_t out = static_cast(rs1 & sourceImm_); results_[0] = RegisterValue(out, 8); break; } @@ -441,8 +429,7 @@ void Instruction::execute() { } case Opcode::RISCV_SLTI: { // SLTI rd,rs1,imm const int64_t rs1 = sourceValues_[0].get(); - const int64_t imm = metadata_.operands[2].imm; - if (rs1 < imm) { + if (rs1 < sourceImm_) { results_[0] = RegisterValue(static_cast(1), 8); } else { results_[0] = RegisterValue(static_cast(0), 8); @@ -451,8 +438,7 @@ void Instruction::execute() { } case Opcode::RISCV_SLTIU: { // SLTIU rd,rs1,imm const uint64_t rs1 = sourceValues_[0].get(); - const uint64_t imm = static_cast(metadata_.operands[2].imm); - if (rs1 < imm) { + if (rs1 < static_cast(sourceImm_)) { results_[0] = RegisterValue(static_cast(1), 8); } else { results_[0] = RegisterValue(static_cast(0), 8); @@ -463,11 +449,11 @@ void Instruction::execute() { const uint64_t rs1 = sourceValues_[0].get(); const uint64_t rs2 = sourceValues_[1].get(); if (rs1 == rs2) { - branchAddress_ = instructionAddress_ + - metadata_.operands[2].imm; // Set LSB of result to 0 + branchAddress_ = + instructionAddress_ + sourceImm_; // Set LSB of result to 0 branchTaken_ = true; } else { - branchAddress_ = instructionAddress_ + 4; + branchAddress_ = instructionAddress_ + metadata_.getInsnLength(); branchTaken_ = false; } break; @@ -476,11 +462,12 @@ void Instruction::execute() { const uint64_t rs1 = sourceValues_[0].get(); const uint64_t rs2 = sourceValues_[1].get(); if (rs1 != rs2) { - branchAddress_ = instructionAddress_ + - metadata_.operands[2].imm; // Set LSB of result to 0 + branchAddress_ = + instructionAddress_ + sourceImm_; // Set LSB of result to 0 branchTaken_ = true; } else { - branchAddress_ = instructionAddress_ + 4; + // Increase by instruction size to account for compressed instructions + branchAddress_ = instructionAddress_ + metadata_.getInsnLength(); branchTaken_ = false; } break; @@ -489,11 +476,11 @@ void Instruction::execute() { const int64_t rs1 = sourceValues_[0].get(); const int64_t rs2 = sourceValues_[1].get(); if (rs1 < rs2) { - branchAddress_ = instructionAddress_ + - metadata_.operands[2].imm; // Set LSB of result to 0 + branchAddress_ = + instructionAddress_ + sourceImm_; // Set LSB of result to 0 branchTaken_ = true; } else { - branchAddress_ = instructionAddress_ + 4; + branchAddress_ = instructionAddress_ + metadata_.getInsnLength(); branchTaken_ = false; } break; @@ -502,11 +489,11 @@ void Instruction::execute() { const uint64_t rs1 = sourceValues_[0].get(); const uint64_t rs2 = sourceValues_[1].get(); if (rs1 < rs2) { - branchAddress_ = instructionAddress_ + - metadata_.operands[2].imm; // Set LSB of result to 0 + branchAddress_ = + instructionAddress_ + sourceImm_; // Set LSB of result to 0 branchTaken_ = true; } else { - branchAddress_ = instructionAddress_ + 4; + branchAddress_ = instructionAddress_ + metadata_.getInsnLength(); branchTaken_ = false; } break; @@ -515,11 +502,11 @@ void Instruction::execute() { const int64_t rs1 = sourceValues_[0].get(); const int64_t rs2 = sourceValues_[1].get(); if (rs1 >= rs2) { - branchAddress_ = instructionAddress_ + - metadata_.operands[2].imm; // Set LSB of result to 0 + branchAddress_ = + instructionAddress_ + sourceImm_; // Set LSB of result to 0 branchTaken_ = true; } else { - branchAddress_ = instructionAddress_ + 4; + branchAddress_ = instructionAddress_ + metadata_.getInsnLength(); branchTaken_ = false; } break; @@ -528,28 +515,29 @@ void Instruction::execute() { const uint64_t rs1 = sourceValues_[0].get(); const uint64_t rs2 = sourceValues_[1].get(); if (rs1 >= rs2) { - branchAddress_ = instructionAddress_ + - metadata_.operands[2].imm; // Set LSB of result to 0 + branchAddress_ = + instructionAddress_ + sourceImm_; // Set LSB of result to 0 branchTaken_ = true; } else { - branchAddress_ = instructionAddress_ + 4; + branchAddress_ = instructionAddress_ + metadata_.getInsnLength(); branchTaken_ = false; } break; } case Opcode::RISCV_JAL: { // JAL rd,imm - branchAddress_ = instructionAddress_ + - metadata_.operands[1].imm; // Set LSB of result to 0 + branchAddress_ = + instructionAddress_ + sourceImm_; // Set LSB of result to 0 branchTaken_ = true; - results_[0] = RegisterValue(instructionAddress_ + 4, 8); + results_[0] = + RegisterValue(instructionAddress_ + metadata_.getInsnLength(), 8); break; } case Opcode::RISCV_JALR: { // JALR rd,rs1,imm - branchAddress_ = - (sourceValues_[0].get() + metadata_.operands[2].imm) & - ~1; // Set LSB of result to 0 + branchAddress_ = (sourceValues_[0].get() + sourceImm_) & + ~1; // Set LSB of result to 0 branchTaken_ = true; - results_[0] = RegisterValue(instructionAddress_ + 4, 8); + results_[0] = + RegisterValue(instructionAddress_ + metadata_.getInsnLength(), 8); break; } // TODO EBREAK @@ -578,8 +566,7 @@ void Instruction::execute() { case Opcode::RISCV_LR_W_RL: case Opcode::RISCV_LR_W_AQ_RL: { // TODO set "reservation set" in memory, currently not needed as all - // codes - // are single threaded + // codes are single threaded // TODO check that address is naturally aligned to operand size, // if not raise address-misaligned/access-fault exception // TODO use aq and rl bits to prevent reordering with other memory diff --git a/src/lib/config/ModelConfig.cc b/src/lib/config/ModelConfig.cc index 9e85fa5aa0..197ed6ea4f 100644 --- a/src/lib/config/ModelConfig.cc +++ b/src/lib/config/ModelConfig.cc @@ -266,12 +266,18 @@ void ModelConfig::setExpectations(bool isDefault) { // Set isa_ if (ISA == "AArch64") { isa_ = ISA::AArch64; - } else if ("rv64") { + } else if (ISA == "rv64") { isa_ = ISA::RV64; } } createGroupMapping(); + if (isa_ == ISA::RV64) { + expectations_["Core"].addChild( + ExpectationNode::createExpectation(false, "Compressed")); + expectations_["Core"]["Compressed"].setValueSet(std::vector{false, true}); + } + expectations_["Core"].addChild( ExpectationNode::createExpectation("emulation", "Simulation-Mode")); diff --git a/src/lib/pipeline/FetchUnit.cc b/src/lib/pipeline/FetchUnit.cc index ad08016dad..a74bf66a05 100644 --- a/src/lib/pipeline/FetchUnit.cc +++ b/src/lib/pipeline/FetchUnit.cc @@ -83,31 +83,38 @@ void FetchUnit::tick() { break; } } - if (fetchIndex == fetched.size()) { - // Need to wait for fetched instructions + // Decide how to progress based on status of fetched data and buffer. Allow + // progression if minimal data is in the buffer no matter state of fetched + // data + if (fetchIndex == fetched.size() && + bufferedBytes_ < isa_.getMinInstructionSize()) { + // Relevant data has not been fetched and not enough data already in the + // buffer. Need to wait for fetched instructions return; + } else if (fetchIndex != fetched.size()) { + // Data has been successfully read, move into fetch buffer + // TODO: Handle memory faults + assert(fetched[fetchIndex].data && "Memory read failed"); + const uint8_t* fetchData = + fetched[fetchIndex].data.getAsVector(); + + // Copy fetched data to fetch buffer after existing data + std::memcpy(fetchBuffer_ + bufferedBytes_, fetchData + bufferOffset, + blockSize_ - bufferOffset); + + bufferedBytes_ += blockSize_ - bufferOffset; + buffer = fetchBuffer_; + // Decoding should start from the beginning of the fetchBuffer_. + bufferOffset = 0; } - - // TODO: Handle memory faults - assert(fetched[fetchIndex].data && "Memory read failed"); - const uint8_t* fetchData = fetched[fetchIndex].data.getAsVector(); - - // Copy fetched data to fetch buffer after existing data - std::memcpy(fetchBuffer_ + bufferedBytes_, fetchData + bufferOffset, - blockSize_ - bufferOffset); - - bufferedBytes_ += blockSize_ - bufferOffset; - buffer = fetchBuffer_; - // Decoding should start from the beginning of the fetchBuffer_. - bufferOffset = 0; } else { // There is already enough data in the fetch buffer, so use that buffer = fetchBuffer_; bufferOffset = 0; } - // Check we have enough data to begin decoding - if (bufferedBytes_ < isa_.getMaxInstructionSize()) return; + // Check we have enough data to begin decoding as read may not have completed + if (bufferedBytes_ < isa_.getMinInstructionSize()) return; auto outputSlots = output_.getTailSlots(); for (size_t slot = 0; slot < output_.getWidth(); slot++) { @@ -164,6 +171,7 @@ void FetchUnit::tick() { assert(bytesRead <= bufferedBytes_ && "Predecode consumed more bytes than were available"); + // Increment the offset, decrement available bytes bufferOffset += bytesRead; bufferedBytes_ -= bytesRead; diff --git a/test/regression/riscv/CMakeLists.txt b/test/regression/riscv/CMakeLists.txt index 1ef42fc1bd..fc2ddab50b 100644 --- a/test/regression/riscv/CMakeLists.txt +++ b/test/regression/riscv/CMakeLists.txt @@ -14,6 +14,7 @@ add_executable(regression-riscv instructions/branch.cc instructions/atomic.cc instructions/float.cc + instructions/compressed.cc ) configure_file(${capstone_SOURCE_DIR}/arch/RISCV/RISCVGenInstrInfo.inc RISCVGenInstrInfo.inc COPYONLY) diff --git a/test/regression/riscv/RISCVRegressionTest.cc b/test/regression/riscv/RISCVRegressionTest.cc index cbe456b479..a75f182fff 100644 --- a/test/regression/riscv/RISCVRegressionTest.cc +++ b/test/regression/riscv/RISCVRegressionTest.cc @@ -5,13 +5,13 @@ using namespace simeng::arch::riscv; -void RISCVRegressionTest::run(const char* source) { +void RISCVRegressionTest::run(const char* source, const char* extensions) { // Initialise LLVM LLVMInitializeRISCVTargetInfo(); LLVMInitializeRISCVTargetMC(); LLVMInitializeRISCVAsmParser(); - RegressionTest::run(source, "riscv64", "+m,+a,+f,+d"); + RegressionTest::run(source, "riscv64", extensions); } void RISCVRegressionTest::generateConfig() const { diff --git a/test/regression/riscv/RISCVRegressionTest.hh b/test/regression/riscv/RISCVRegressionTest.hh index 61beed0753..ac12a8e6cc 100644 --- a/test/regression/riscv/RISCVRegressionTest.hh +++ b/test/regression/riscv/RISCVRegressionTest.hh @@ -58,12 +58,27 @@ inline std::string paramToString( * are appended to the source to ensure that the program will terminate with * an unallocated instruction encoding exception instead of running into the * heap. */ -#define RUN_RISCV(source) \ - { \ - std::string sourceWithTerminator = source; \ - sourceWithTerminator += "\n.word 0"; \ - run(sourceWithTerminator.c_str()); \ - } \ +#define RUN_RISCV(source) \ + { \ + std::string sourceWithTerminator = source; \ + sourceWithTerminator += "\n.word 0"; \ + run(sourceWithTerminator.c_str(), "+m,+a,+f,+d"); \ + } \ + if (HasFatalFailure()) return + +/** A helper macro to run a snippet of RISCV assembly code, returning from + * the calling function if a fatal error occurs. Four bytes containing zeros + * are appended to the source to ensure that the program will terminate with + * an illegal instruction exception instead of running into the heap. This + * specifically targets the compressed extension allowing for the RUN_RISCV + * macro to ignore it, otherwise LLVM eagerly emits compressed instructions for + * non-compressed assembly. */ +#define RUN_RISCV_COMP(source) \ + { \ + std::string sourceWithTerminator = source; \ + sourceWithTerminator += "\n.word 0"; \ + run(sourceWithTerminator.c_str(), "+m,+a,+f,+d,+c"); \ + } \ if (HasFatalFailure()) return /** The test fixture for all RISCV regression tests. */ @@ -72,7 +87,7 @@ class RISCVRegressionTest : public RegressionTest { virtual ~RISCVRegressionTest() {} /** Run the assembly code in `source`. */ - void run(const char* source); + void run(const char* source, const char* extensions); /** Generate a default YAML-formatted configuration. */ void generateConfig() const override; diff --git a/test/regression/riscv/instructions/compressed.cc b/test/regression/riscv/instructions/compressed.cc new file mode 100644 index 0000000000..4b68edb76f --- /dev/null +++ b/test/regression/riscv/instructions/compressed.cc @@ -0,0 +1,727 @@ +#include "RISCVRegressionTest.hh" + +namespace { + +using InstCompressed = RISCVRegressionTest; + +TEST_P(InstCompressed, lwsp) { + // Load word from mem[stack pointer + imm] + initialHeapData_.resize(16); + uint32_t* heap = reinterpret_cast(initialHeapData_.data()); + heap[0] = 0xDEADBEEF; + heap[1] = 0x12345678; + heap[2] = 0xFEEBDAED; + heap[3] = 0x87654321; + + RUN_RISCV_COMP(R"( + li a7, 214 + ecall + + li x2, 0 + add x2, x2, a0 + c.lwsp t6, 0(x2) + c.lwsp t4, 4(x2) + )"); + EXPECT_EQ(getGeneralRegister(31), 0xFFFFFFFFDEADBEEF); + EXPECT_EQ(getGeneralRegister(29), 0x0000000012345678); +} + +TEST_P(InstCompressed, ldsp) { + // Load double word from mem[stack pointer + imm] + initialHeapData_.resize(16); + uint32_t* heap = reinterpret_cast(initialHeapData_.data()); + heap[0] = 0xDEADBEEF; + heap[1] = 0x12345678; + heap[2] = 0xFEEBDAED; + heap[3] = 0x87654321; + + RUN_RISCV_COMP(R"( + li a7, 214 + ecall + + li x2, 0 + add x2, x2, a0 + c.ldsp t6, 0(x2) + addi x2, x2, -4 + c.ldsp t4, 8(x2) + )"); + EXPECT_EQ(getGeneralRegister(31), 0x12345678DEADBEEF); + EXPECT_EQ(getGeneralRegister(29), 0xFEEBDAED12345678); +} + +TEST_P(InstCompressed, fldsp) { + // Load double precision float from mem[stack pointer + imm] + initialHeapData_.resize(32); + double* heap = reinterpret_cast(initialHeapData_.data()); + heap[0] = 1.0; + heap[1] = 123.456; + heap[2] = -0.00032; + heap[3] = 123456; + + RUN_RISCV_COMP(R"( + # Get heap address + li a7, 214 + ecall + + li x2, 0 + add x2, x2, a0 + c.fldsp ft0, 0(x2) + c.fldsp ft1, 8(x2) + c.fldsp ft2, 16(x2) + c.fldsp ft3, 24(x2) + )"); + + EXPECT_EQ(getFPRegister(0), 1.0); + EXPECT_EQ(getFPRegister(1), 123.456); + EXPECT_EQ(getFPRegister(2), -0.00032); + EXPECT_EQ(getFPRegister(3), 123456); +} + +TEST_P(InstCompressed, swsp) { + // Store word at mem[stack pointer + imm] + RUN_RISCV_COMP(R"( + li t6, 0xAA + c.swsp t6, 0(sp) + + addi t6, t6, 0xAA # 0xAA + 0xAA = 154 + slli t6, t6, 16 + addi t6, t6, 0xAA # 0x15400AA + c.swsp t6, 4(sp) + )"); + EXPECT_EQ(getMemoryValue(process_->getInitialStackPointer()), + 0x000000AA); + EXPECT_EQ(getMemoryValue(process_->getInitialStackPointer()), + 0x15400AA000000AA); +} + +TEST_P(InstCompressed, sdsp) { + // Store double word at mem[stack pointer + imm] + RUN_RISCV_COMP(R"( + li t6, 0xAA + c.sdsp t6, 0(sp) + + addi t6, t6, 0xAA # 0xAA + 0xAA = 154 + slli t6, t6, 16 + addi t6, t6, 0xAA # 0x15400AA + c.sdsp t6, 8(sp) + )"); + EXPECT_EQ(getMemoryValue(process_->getInitialStackPointer()), + 0x00000000000000AA); + EXPECT_EQ(getMemoryValue(process_->getInitialStackPointer() + 8), + 0x00000000015400AA); +} + +TEST_P(InstCompressed, fsdsp) { + // Store double precision float at mem[stack pointer + imm] + RUN_RISCV_COMP(R"( + li t6, 0xAA + fmv.d.x f8, t6 + c.fsdsp f8, 0(sp) + + addi t6, t6, 0xAA # 0xAA + 0xAA = 154 + slli t6, t6, 16 + addi t6, t6, 0xAA # 0x15400AA + fmv.d.x f8, t6 + c.fsdsp f8, 8(sp) + )"); + EXPECT_EQ(getMemoryValue(process_->getInitialStackPointer()), + 0x00000000000000AA); + EXPECT_EQ(getMemoryValue(process_->getInitialStackPointer() + 8), + 0x00000000015400AA); +} + +TEST_P(InstCompressed, lw) { + // Compressed load word + initialHeapData_.resize(16); + uint32_t* heap = reinterpret_cast(initialHeapData_.data()); + heap[0] = 0xDEADBEEF; + heap[1] = 0x12345678; + heap[2] = 0xFEEBDAED; + heap[3] = 0x87654321; + + RUN_RISCV_COMP(R"( + li a7, 214 + ecall + + add x8, x8, a0 + c.lw x15, 0(x8) + c.lw x13, 4(x8) + )"); + EXPECT_EQ(getGeneralRegister(15), 0xFFFFFFFFDEADBEEF); + EXPECT_EQ(getGeneralRegister(13), 0x0000000012345678); +} + +TEST_P(InstCompressed, ld) { + // Compressed store word + initialHeapData_.resize(16); + uint32_t* heap = reinterpret_cast(initialHeapData_.data()); + heap[0] = 0xDEADBEEF; + heap[1] = 0x12345678; + heap[2] = 0xFEEBDAED; + heap[3] = 0x87654321; + + RUN_RISCV_COMP(R"( + li a7, 214 + ecall + + add x8, x8, a0 + c.ld x15, 0(x8) + addi x8, x8, -4 + c.ld x13, 8(x8) + )"); + EXPECT_EQ(getGeneralRegister(15), 0x12345678DEADBEEF); + EXPECT_EQ(getGeneralRegister(13), 0xFEEBDAED12345678); +} + +TEST_P(InstCompressed, fld) { + // Compressed load double precision float + initialHeapData_.resize(32); + double* heap = reinterpret_cast(initialHeapData_.data()); + heap[0] = 1.0; + heap[1] = 123.456; + heap[2] = -0.00032; + heap[3] = 123456; + + RUN_RISCV_COMP(R"( + # Get heap address + li a7, 214 + ecall + + c.fld f8, 0(a0) + c.fld f9, 8(a0) + c.fld f10, 16(a0) + c.fld f11, 24(a0) + )"); + + EXPECT_EQ(getFPRegister(8), 1.0); + EXPECT_EQ(getFPRegister(9), 123.456); + EXPECT_EQ(getFPRegister(10), -0.00032); + EXPECT_EQ(getFPRegister(11), 123456); +} + +TEST_P(InstCompressed, sw) { + // Compressed store word + initialHeapData_.resize(16); + uint32_t* heap = reinterpret_cast(initialHeapData_.data()); + heap[0] = 0x12345678; + heap[1] = 0xDEADBEEF; + heap[2] = 0x87654321; + + RUN_RISCV_COMP(R"( + # Get heap address + li a7, 214 + ecall + + li x8, 0xAA + c.sw x8, 0(a0) + + addi x8, x8, 0xAA # 0xAA + 0xAA = 154 + slli x8, x8, 16 + addi x8, x8, 0xAA # 0x15400AA + c.sw x8, 4(a0) + )"); + + EXPECT_EQ(getGeneralRegister(10), 32); + EXPECT_EQ(getMemoryValue(32), 0x015400AA000000AA); + EXPECT_EQ(getMemoryValue(36), 0x87654321015400AA); +} + +TEST_P(InstCompressed, sd) { + // Compressed store double word + initialHeapData_.resize(16); + uint32_t* heap = reinterpret_cast(initialHeapData_.data()); + heap[0] = 0x12345678; + heap[1] = 0xDEADBEEF; + heap[2] = 0x87654321; + + RUN_RISCV_COMP(R"( + # Get heap address + li a7, 214 + ecall + + li x8, 0xAA + c.sd x8, 0(a0) + + addi x8, x8, 0xAA # 0xAA + 0xAA = 154 + slli x8, x8, 16 + addi x8, x8, 0xAA # 0x15400AA + c.sd x8, 8(a0) + )"); + + EXPECT_EQ(getGeneralRegister(10), 32); + EXPECT_EQ(getMemoryValue(32), 0x00000000000000AA); + EXPECT_EQ(getMemoryValue(40), 0x00000000015400AA); +} + +TEST_P(InstCompressed, fsd) { + // Compressed store double precision float + initialHeapData_.resize(32); + double* heap = reinterpret_cast(initialHeapData_.data()); + heap[0] = 1.0; + heap[1] = 123.456; + heap[2] = -0.00032; + heap[3] = 123456; + + RUN_RISCV_COMP(R"( + # Get heap address + li a7, 214 + ecall + + fld fa0, 0(a0) + fld fa1, 8(a0) + fld fa2, 16(a0) + fld fa3, 24(a0) + + c.fsd fa3, 0(a0) + c.fsd fa2, 8(a0) + c.fsd fa1, 16(a0) + c.fsd fa0, 24(a0) + )"); + + EXPECT_EQ(getFPRegister(10), 1.0); + EXPECT_EQ(getFPRegister(11), 123.456); + EXPECT_EQ(getFPRegister(12), -0.00032); + EXPECT_EQ(getFPRegister(13), 123456); + + EXPECT_EQ(getGeneralRegister(10), 32); + + EXPECT_EQ(getMemoryValue(32), 123456); + EXPECT_EQ(getMemoryValue(40), -0.00032); + EXPECT_EQ(getMemoryValue(48), 123.456); + EXPECT_EQ(getMemoryValue(56), 1.0); +} + +TEST_P(InstCompressed, j) { + // Compressed jump + // Labels needed as LLVM eagerly uses compressed instructions e.g. addi -> + // c.addi causing manual jump offsets to become seemingly misaligned with the + // values used in the tests + RUN_RISCV_COMP(R"( + c.j jump #c.j 0xc + jumpa: + addi t6, t6, 10 + jal t1, jumpc #jal t1, 0xc + jump: + addi t5, t5, 5 + jal jumpa #jal -0xc + jumpc: + addi t4, t4, 3 + )"); + EXPECT_EQ(getGeneralRegister(30), 5); + EXPECT_EQ(getGeneralRegister(31), 10); + EXPECT_EQ(getGeneralRegister(29), 3); + EXPECT_EQ(getGeneralRegister(6), 8); + EXPECT_EQ(getGeneralRegister(1), 14); + EXPECT_EQ(getGeneralRegister(0), 0); +} + +TEST_P(InstCompressed, jr) { + // Compressed jump to address in register + RUN_RISCV_COMP(R"( + c.addi x9, 8 + c.jr x9 + c.addi x8, 4 + c.j end + c.addi x8, 5 + end: + )"); + EXPECT_EQ(getGeneralRegister(8), 5); +} + +TEST_P(InstCompressed, jalr) { + // Compressed jump to address in rs1, save pc+2 in link register + RUN_RISCV_COMP(R"( + li x8, 12 + c.jalr x8 + mv t0, ra + addi t6, t6, 10 + li x8, 20 + c.jalr x8 + mv t1, ra + addi t5, t5, 5 + li x8, 4 + c.jalr x8 + mv t2, ra + addi t4, t4, 3 + )"); + EXPECT_EQ(getGeneralRegister(30), 5); + EXPECT_EQ(getGeneralRegister(31), 10); + EXPECT_EQ(getGeneralRegister(29), 3); + EXPECT_EQ(getGeneralRegister(5), 20); + EXPECT_EQ(getGeneralRegister(6), 4); + EXPECT_EQ(getGeneralRegister(7), 12); + EXPECT_EQ(getGeneralRegister(1), 12); +} + +TEST_P(InstCompressed, beqz) { + // Compressed branch if rs1 equal to zero + RUN_RISCV_COMP(R"( + addi x8, x8, 2 + c.beqz x8, b1 + addi x10, x10, 10 + li x9, 0 + c.beqz x9, b2 + j b3 + b1: + addi x10, x10, 5 + b2: + addi x11, x11, 10 + j b4 + b3: + addi x11, x11, 5 + b4: + )"); + EXPECT_EQ(getGeneralRegister(10), 10); + EXPECT_EQ(getGeneralRegister(11), 10); +} + +TEST_P(InstCompressed, bnez) { + // Compressed branch if rs1 not equal to zero + RUN_RISCV_COMP(R"( + addi x8, x8, 0 + c.bnez x8, b1 + addi x10, x10, 10 + li x9, 2 + c.bnez x9, b2 + j b3 + b1: + addi x10, x10, 5 + b2: + addi x11, x11, 10 + j b4 + b3: + addi x11, x11, 5 + b4: + )"); +} + +TEST_P(InstCompressed, li) { + // Compressed load immediate + RUN_RISCV_COMP(R"( + addi a5, a5, 12 + c.li a5, 0 + addi a4, a4, 12 + c.li a4, -32 + addi a3, a3, 12 + c.li a3, 31 + )"); + EXPECT_EQ(getGeneralRegister(15), 0); + EXPECT_EQ(getGeneralRegister(14), -32); + EXPECT_EQ(getGeneralRegister(13), 31); +} + +TEST_P(InstCompressed, lui) { + // Compressed load immediate into bits 17-12, clear bottom 12 and sign extend + // high bits + RUN_RISCV_COMP(R"( + c.lui t3, 4 + c.lui t4, 0xFFFFC + )"); + EXPECT_EQ(getGeneralRegister(28), 4 << 12); + EXPECT_EQ(getGeneralRegister(29), -4ull << 12); +} + +TEST_P(InstCompressed, addi) { + // Compressed add immediate + RUN_RISCV_COMP(R"( + c.addi t3, 3 + c.addi t4, 6 + c.addi t3, 30 + c.addi zero, 16 + )"); + EXPECT_EQ(getGeneralRegister(29), 6u); + EXPECT_EQ(getGeneralRegister(28), 33u); + EXPECT_EQ(getGeneralRegister(0), 0); +} + +TEST_P(InstCompressed, addiw) { + // Compressed add immediate. Produces 32 bit result and sign extends + RUN_RISCV_COMP(R"( + addi t3, t3, 91 + slli t3, t3, 28 + addiw t5, t3, -5 + addiw t6, t2, -5 + )"); + EXPECT_EQ(getGeneralRegister(28), 24427626496); + EXPECT_EQ(getGeneralRegister(30), -1342177285); + EXPECT_EQ(getGeneralRegister(31), -5); +} + +TEST_P(InstCompressed, addi16sp) { + // Add immediate (multiple of 16) to stack pointer + RUN_RISCV_COMP(R"( + mv x8, sp + c.addi16sp x2, 16 + mv x9, x2 + )"); + EXPECT_EQ(getGeneralRegister(8), + process_->getInitialStackPointer()); + EXPECT_EQ(getGeneralRegister(9), + process_->getInitialStackPointer() + 16); +} + +TEST_P(InstCompressed, addi4spn) { + // Add immediate to stack pointer + RUN_RISCV_COMP(R"( + c.addi4spn x8, x2, 4 + c.addi4spn x9, x2, 12 + )"); + EXPECT_EQ(getGeneralRegister(8), + process_->getInitialStackPointer() + 4); + EXPECT_EQ(getGeneralRegister(9), + process_->getInitialStackPointer() + 12); +} + +TEST_P(InstCompressed, slli) { + // Compressed shift left logical by immediate. rs1 = rd + RUN_RISCV_COMP(R"( + addi t4, t4, 6 + c.slli t4, 5 + )"); + EXPECT_EQ(getGeneralRegister(29), 192); +} + +TEST_P(InstCompressed, srli) { + // Compressed shift right logical by immediate. rs1 = rd + RUN_RISCV_COMP(R"( + addi x8, x8, -4 + c.srli x8, 61 + )"); + EXPECT_EQ(getGeneralRegister(8), 7); +} + +TEST_P(InstCompressed, srai) { + // Compressed shift right arithmetic by immediate. rs1 = rd + RUN_RISCV_COMP(R"( + addi x8, x8, -4 + add t0, t0, x8 + c.srai x8, 1 + addi x9, t0, 8 + c.srai x9, 1 + )"); + EXPECT_EQ(getGeneralRegister(8), -2); + EXPECT_EQ(getGeneralRegister(9), 2); +} + +TEST_P(InstCompressed, andi) { + // Compressed AND with sign extended immediate. rs1 = rd + RUN_RISCV_COMP(R"( + addi x9, x9, 3 + addi t4, t4, 5 + and x8, x9, t4 + c.andi x8, 9 + c.andi x9, -7 + )"); + EXPECT_EQ(getGeneralRegister(8), 0b0001); + EXPECT_EQ(getGeneralRegister(9), 1); +} + +TEST_P(InstCompressed, mv) { + // Compressed move + RUN_RISCV_COMP(R"( + addi x8, x8, 3 + addi x9, x9, 6 + c.mv x8, x9 + )"); + EXPECT_EQ(getGeneralRegister(8), 6u); + EXPECT_EQ(getGeneralRegister(9), 6u); +} + +TEST_P(InstCompressed, add) { + // Compressed add. rs1 = rd + RUN_RISCV_COMP(R"( + addi x8, x8, 3 + addi x9, x9, 6 + c.add x8, x9 + )"); + EXPECT_EQ(getGeneralRegister(8), 9u); + EXPECT_EQ(getGeneralRegister(9), 6u); +} + +TEST_P(InstCompressed, and) { + // Compressed AND. rs1 = rd + RUN_RISCV_COMP(R"( + addi x8, x8, 3 + addi x9, x9, 5 + c.and x8, x9 + )"); + EXPECT_EQ(getGeneralRegister(8), 0b0001); +} + +TEST_P(InstCompressed, or) { + // Compressed OR. rs1 = rd + RUN_RISCV_COMP(R"( + addi x8, x8, 3 + addi x9, x9, 5 + c.or x8, x9 + )"); + EXPECT_EQ(getGeneralRegister(8), 0b0111); +} + +TEST_P(InstCompressed, xor) { + // Compressed XOR. rs1 = rd + RUN_RISCV_COMP(R"( + addi x8, x8, 3 + addi x9, x9, 5 + c.xor x8, x9 + )"); + EXPECT_EQ(getGeneralRegister(8), 0b0110); +} + +TEST_P(InstCompressed, sub) { + // Compressed subtract. rs1 = rd + RUN_RISCV_COMP(R"( + addi x8, x8, 3 + addi x9, x9, 6 + mv x10, x8 + c.sub x8, x9 + c.sub x9, x10 + )"); + EXPECT_EQ(getGeneralRegister(8), -3); + EXPECT_EQ(getGeneralRegister(9), 3); +} + +TEST_P(InstCompressed, addw) { + // Compressed add word. Adds rd and rs2 then sign extends lower 32 bits. rs1 = + // rd + RUN_RISCV_COMP(R"( + addi x9, x9, -7 + addi x8, x8, 3 + mv x11, x8 + addi x10, x10, 6 + c.addw x8, x10 + c.addw x9, x11 + )"); + EXPECT_EQ(getGeneralRegister(8), 9u); + EXPECT_EQ(getGeneralRegister(9), -4); +} + +TEST_P(InstCompressed, subw) { + // Compressed subtract word. Subtracts rs2 from rd then sign extends lower 32 + // bits. rs1 = rd + RUN_RISCV_COMP(R"( + addi x9, x9, 3 + addi x10, x10, 6 + mv x11, x10 + mv x12, x9 + c.subw x9, x10 + c.subw x10, x12 + + li x12, -1 + addi x11, x11, -8 + c.subw x12, x11 + )"); + EXPECT_EQ(getGeneralRegister(9), -3); + EXPECT_EQ(getGeneralRegister(10), 3); + + EXPECT_EQ(getGeneralRegister(11), -2); + EXPECT_EQ(getGeneralRegister(12), 0x0000000000000001); +} + +TEST_P(InstCompressed, nop) { + // Ensure that a nop doesn't change the state of the processor + // Load a register and check initial architectural state + RUN_RISCV_COMP(R"( + li x8, 1234 + )"); + EXPECT_EQ(getGeneralRegister(0), 0); + EXPECT_EQ(getGeneralRegister(1), 0); + EXPECT_EQ(getGeneralRegister(2), 199840); + EXPECT_EQ(getGeneralRegister(3), 0); + EXPECT_EQ(getGeneralRegister(4), 0); + EXPECT_EQ(getGeneralRegister(5), 0); + EXPECT_EQ(getGeneralRegister(6), 0); + EXPECT_EQ(getGeneralRegister(7), 0); + EXPECT_EQ(getGeneralRegister(8), 1234); + EXPECT_EQ(getGeneralRegister(9), 0); + EXPECT_EQ(getGeneralRegister(10), 0); + EXPECT_EQ(getGeneralRegister(11), 0); + EXPECT_EQ(getGeneralRegister(12), 0); + EXPECT_EQ(getGeneralRegister(13), 0); + EXPECT_EQ(getGeneralRegister(14), 0); + EXPECT_EQ(getGeneralRegister(15), 0); + EXPECT_EQ(getGeneralRegister(16), 0); + EXPECT_EQ(getGeneralRegister(17), 0); + EXPECT_EQ(getGeneralRegister(18), 0); + EXPECT_EQ(getGeneralRegister(19), 0); + EXPECT_EQ(getGeneralRegister(20), 0); + EXPECT_EQ(getGeneralRegister(21), 0); + EXPECT_EQ(getGeneralRegister(22), 0); + EXPECT_EQ(getGeneralRegister(23), 0); + EXPECT_EQ(getGeneralRegister(24), 0); + EXPECT_EQ(getGeneralRegister(25), 0); + EXPECT_EQ(getGeneralRegister(26), 0); + EXPECT_EQ(getGeneralRegister(27), 0); + EXPECT_EQ(getGeneralRegister(28), 0); + EXPECT_EQ(getGeneralRegister(29), 0); + EXPECT_EQ(getGeneralRegister(30), 0); + EXPECT_EQ(getGeneralRegister(31), 0); + EXPECT_EQ(numTicks_, 2); // 1 insn + 1 for unimplemented final insn + + numTicks_ = 0; + + // Run some no operations + RUN_RISCV_COMP(R"( + c.nop + c.nop + c.nop + c.nop + c.nop + )"); + + // Ensure state hasn't changed except the number of ticks + EXPECT_EQ(getGeneralRegister(0), 0); + EXPECT_EQ(getGeneralRegister(1), 0); + EXPECT_EQ(getGeneralRegister(2), 199840); + EXPECT_EQ(getGeneralRegister(3), 0); + EXPECT_EQ(getGeneralRegister(4), 0); + EXPECT_EQ(getGeneralRegister(5), 0); + EXPECT_EQ(getGeneralRegister(6), 0); + EXPECT_EQ(getGeneralRegister(7), 0); + EXPECT_EQ(getGeneralRegister(8), 0); + EXPECT_EQ(getGeneralRegister(9), 0); + EXPECT_EQ(getGeneralRegister(10), 0); + EXPECT_EQ(getGeneralRegister(11), 0); + EXPECT_EQ(getGeneralRegister(12), 0); + EXPECT_EQ(getGeneralRegister(13), 0); + EXPECT_EQ(getGeneralRegister(14), 0); + EXPECT_EQ(getGeneralRegister(15), 0); + EXPECT_EQ(getGeneralRegister(16), 0); + EXPECT_EQ(getGeneralRegister(17), 0); + EXPECT_EQ(getGeneralRegister(18), 0); + EXPECT_EQ(getGeneralRegister(19), 0); + EXPECT_EQ(getGeneralRegister(20), 0); + EXPECT_EQ(getGeneralRegister(21), 0); + EXPECT_EQ(getGeneralRegister(22), 0); + EXPECT_EQ(getGeneralRegister(23), 0); + EXPECT_EQ(getGeneralRegister(24), 0); + EXPECT_EQ(getGeneralRegister(25), 0); + EXPECT_EQ(getGeneralRegister(26), 0); + EXPECT_EQ(getGeneralRegister(27), 0); + EXPECT_EQ(getGeneralRegister(28), 0); + EXPECT_EQ(getGeneralRegister(29), 0); + EXPECT_EQ(getGeneralRegister(30), 0); + EXPECT_EQ(getGeneralRegister(31), 0); + EXPECT_EQ(numTicks_, 6); // 5 insns + 1 for unimplemented final insn +} + +TEST_P(InstCompressed, ebreak) { + // Currently not implemented so ensure this produces an exception + + RUN_RISCV_COMP(R"( + c.ebreak + )"); + + const char err1[] = + "\n[SimEng:ExceptionHandler] Encountered execution not-yet-implemented " + "exception\n[SimEng:ExceptionHandler] Generated by instruction: " + "\n[SimEng:ExceptionHandler] 0x0000000000000000: 02 90 c.ebreak"; + EXPECT_EQ(stdout_.substr(0, sizeof(err1) - 1), err1); +} + +INSTANTIATE_TEST_SUITE_P( + RISCV, InstCompressed, + ::testing::Values(std::make_tuple(EMULATION, "{Core: {Compressed: True}}")), + paramToString); + +} // namespace diff --git a/test/regression/riscv/instructions/jump.cc b/test/regression/riscv/instructions/jump.cc index 97d71b84dd..968e03fd99 100644 --- a/test/regression/riscv/instructions/jump.cc +++ b/test/regression/riscv/instructions/jump.cc @@ -6,6 +6,7 @@ using InstJump = RISCVRegressionTest; TEST_P(InstJump, jalr) { RUN_RISCV(R"( + li t1, 4 jalr t0, t1, 12 addi t6, t6, 10 jalr ra, t1, 20 @@ -16,8 +17,8 @@ TEST_P(InstJump, jalr) { EXPECT_EQ(getGeneralRegister(30), 5); EXPECT_EQ(getGeneralRegister(31), 10); EXPECT_EQ(getGeneralRegister(29), 3); - EXPECT_EQ(getGeneralRegister(1), 12); - EXPECT_EQ(getGeneralRegister(5), 4); + EXPECT_EQ(getGeneralRegister(1), 16); + EXPECT_EQ(getGeneralRegister(5), 8); } TEST_P(InstJump, jalrAlias) { diff --git a/test/unit/MockArchitecture.hh b/test/unit/MockArchitecture.hh index 2658bf84aa..c89e519ef4 100644 --- a/test/unit/MockArchitecture.hh +++ b/test/unit/MockArchitecture.hh @@ -20,6 +20,7 @@ class MockArchitecture : public arch::Architecture { const Core& core, memory::MemoryInterface& memory)); MOCK_CONST_METHOD0(getInitialState, arch::ProcessStateChange()); MOCK_CONST_METHOD0(getMaxInstructionSize, uint8_t()); + MOCK_CONST_METHOD0(getMinInstructionSize, uint8_t()); MOCK_CONST_METHOD2(updateSystemTimerRegisters, void(RegisterFileSet* regFile, const uint64_t iterations)); }; diff --git a/test/unit/pipeline/FetchUnitTest.cc b/test/unit/pipeline/FetchUnitTest.cc index ca36fb0383..8ecdc7d88b 100644 --- a/test/unit/pipeline/FetchUnitTest.cc +++ b/test/unit/pipeline/FetchUnitTest.cc @@ -25,7 +25,8 @@ using ::testing::SetArgReferee; namespace simeng { namespace pipeline { -class PipelineFetchUnitTest : public testing::Test { +class PipelineFetchUnitTest + : public testing::TestWithParam> { public: PipelineFetchUnitTest() : output(1, {}), @@ -43,7 +44,9 @@ class PipelineFetchUnitTest : public testing::Test { } protected: - const uint8_t insnMaxSizeBytes = 4; + const uint8_t insnMinSizeBytes = GetParam().first; + const uint8_t insnMaxSizeBytes = GetParam().second; + // TODO make this parameterisable and update all tests accordingly const uint8_t blockSize = 16; PipelineBuffer output; @@ -65,7 +68,7 @@ class PipelineFetchUnitTest : public testing::Test { // Tests that ticking a fetch unit attempts to predecode from the correct // program counter and generates output correctly. -TEST_F(PipelineFetchUnitTest, Tick) { +TEST_P(PipelineFetchUnitTest, Tick) { MacroOp macroOp = {uopPtr}; ON_CALL(memory, getCompletedReads()).WillByDefault(Return(completedReads)); @@ -83,7 +86,7 @@ TEST_F(PipelineFetchUnitTest, Tick) { } // Tests that ticking a fetch unit does nothing if the output has stalled -TEST_F(PipelineFetchUnitTest, TickStalled) { +TEST_P(PipelineFetchUnitTest, TickStalled) { output.stall(true); // Anticipate testing instruction type; return true for branch @@ -101,14 +104,19 @@ TEST_F(PipelineFetchUnitTest, TickStalled) { // Tests that the fetch unit will handle instructions that straddle fetch block // boundaries by automatically requesting the next block of data. -TEST_F(PipelineFetchUnitTest, FetchUnaligned) { +TEST_P(PipelineFetchUnitTest, FetchUnaligned) { MacroOp mOp = {uopPtr}; ON_CALL(isa, getMaxInstructionSize()).WillByDefault(Return(insnMaxSizeBytes)); + ON_CALL(isa, getMinInstructionSize()).WillByDefault(Return(insnMinSizeBytes)); ON_CALL(memory, getCompletedReads()).WillByDefault(Return(completedReads)); - // Set PC to 14, so there will not be enough data to start decoding + // Min instruction size needs to be more than 1 to set PC correctly for this + // test + EXPECT_GT(insnMinSizeBytes, 1); + uint64_t setPC = (blockSize - insnMinSizeBytes) + 1; + // Set PC so that there will not be enough data to start decoding EXPECT_CALL(isa, predecode(_, _, _, _)).Times(0); - fetchUnit.updatePC(14); + fetchUnit.updatePC(setPC); fetchUnit.tick(); // Expect a block starting at address 16 to be requested when we fetch again @@ -125,7 +133,8 @@ TEST_F(PipelineFetchUnitTest, FetchUnaligned) { .WillByDefault(DoAll(SetArgReferee<3>(mOp), Return(4))); EXPECT_CALL(memory, getCompletedReads()).Times(1); EXPECT_CALL(memory, clearCompletedReads()).Times(4); - EXPECT_CALL(isa, getMaxInstructionSize()).Times(8); + EXPECT_CALL(isa, getMaxInstructionSize()).Times(4); + EXPECT_CALL(isa, getMinInstructionSize()).Times(4); EXPECT_CALL(isa, predecode(_, _, _, _)).Times(4); // Tick 4 times to process all 16 bytes of fetched data @@ -135,16 +144,18 @@ TEST_F(PipelineFetchUnitTest, FetchUnaligned) { // Tick a 5th time to ensure all buffered bytes have been used EXPECT_CALL(memory, getCompletedReads()).Times(1); EXPECT_CALL(isa, getMaxInstructionSize()).Times(1); + EXPECT_CALL(isa, getMinInstructionSize()).Times(1); EXPECT_CALL(isa, predecode(_, _, _, _)).Times(0); fetchUnit.tick(); } // Tests that a properly aligned PC (to the fetch block boundary) is correctly // fetched -TEST_F(PipelineFetchUnitTest, fetchAligned) { +TEST_P(PipelineFetchUnitTest, fetchAligned) { const uint8_t pc = 16; ON_CALL(isa, getMaxInstructionSize()).WillByDefault(Return(insnMaxSizeBytes)); + ON_CALL(isa, getMinInstructionSize()).WillByDefault(Return(insnMinSizeBytes)); memory::MemoryAccessTarget target = {pc, blockSize}; EXPECT_CALL(isa, getMaxInstructionSize()).Times(1); @@ -163,7 +174,8 @@ TEST_F(PipelineFetchUnitTest, fetchAligned) { .WillByDefault(DoAll(SetArgReferee<3>(mOp), Return(4))); EXPECT_CALL(memory, getCompletedReads()).Times(1); EXPECT_CALL(memory, clearCompletedReads()).Times(4); - EXPECT_CALL(isa, getMaxInstructionSize()).Times(8); + EXPECT_CALL(isa, getMaxInstructionSize()).Times(4); + EXPECT_CALL(isa, getMinInstructionSize()).Times(4); EXPECT_CALL(isa, predecode(_, _, _, _)).Times(4); // Tick 4 times to process all 16 bytes of fetched data @@ -174,13 +186,15 @@ TEST_F(PipelineFetchUnitTest, fetchAligned) { EXPECT_CALL(memory, getCompletedReads()).Times(1); EXPECT_CALL(memory, clearCompletedReads()).Times(0); EXPECT_CALL(isa, getMaxInstructionSize()).Times(1); + EXPECT_CALL(isa, getMinInstructionSize()).Times(1); EXPECT_CALL(isa, predecode(_, _, _, _)).Times(0); fetchUnit.tick(); } // Tests that halting functionality triggers correctly -TEST_F(PipelineFetchUnitTest, halted) { +TEST_P(PipelineFetchUnitTest, halted) { ON_CALL(isa, getMaxInstructionSize()).WillByDefault(Return(insnMaxSizeBytes)); + ON_CALL(isa, getMinInstructionSize()).WillByDefault(Return(insnMinSizeBytes)); EXPECT_FALSE(fetchUnit.hasHalted()); fetchUnit.tick(); EXPECT_FALSE(fetchUnit.hasHalted()); @@ -195,6 +209,7 @@ TEST_F(PipelineFetchUnitTest, halted) { memory::MemoryAccessTarget target = {1008, blockSize}; EXPECT_CALL(isa, getMaxInstructionSize()).Times(1); + EXPECT_CALL(isa, getMinInstructionSize()).Times(0); EXPECT_CALL(memory, requestRead(target, _)).Times(1); fetchUnit.requestFromPC(); @@ -207,7 +222,8 @@ TEST_F(PipelineFetchUnitTest, halted) { .WillByDefault(DoAll(SetArgReferee<3>(mOp), Return(4))); EXPECT_CALL(memory, getCompletedReads()).Times(1); EXPECT_CALL(memory, clearCompletedReads()).Times(4); - EXPECT_CALL(isa, getMaxInstructionSize()).Times(8); + EXPECT_CALL(isa, getMaxInstructionSize()).Times(4); + EXPECT_CALL(isa, getMinInstructionSize()).Times(4); EXPECT_CALL(isa, predecode(_, _, _, _)).Times(4); // Tick 4 times to process all 16 bytes of fetched data for (int i = 0; i < 4; i++) { @@ -218,13 +234,15 @@ TEST_F(PipelineFetchUnitTest, halted) { // Tests that fetching a branch instruction (predicted taken) mid block causes a // branch stall + discards the remaining fetched instructions -TEST_F(PipelineFetchUnitTest, fetchTakenBranchMidBlock) { +TEST_P(PipelineFetchUnitTest, fetchTakenBranchMidBlock) { const uint8_t pc = 16; ON_CALL(isa, getMaxInstructionSize()).WillByDefault(Return(insnMaxSizeBytes)); + ON_CALL(isa, getMinInstructionSize()).WillByDefault(Return(insnMinSizeBytes)); memory::MemoryAccessTarget target = {pc, blockSize}; EXPECT_CALL(isa, getMaxInstructionSize()).Times(1); + EXPECT_CALL(isa, getMinInstructionSize()).Times(0); EXPECT_CALL(memory, requestRead(target, _)).Times(1); // Request block from memory @@ -242,7 +260,8 @@ TEST_F(PipelineFetchUnitTest, fetchTakenBranchMidBlock) { // For first tick, process instruction as non-branch EXPECT_CALL(memory, clearCompletedReads()).Times(1); - EXPECT_CALL(isa, getMaxInstructionSize()).Times(2); + EXPECT_CALL(isa, getMaxInstructionSize()).Times(1); + EXPECT_CALL(isa, getMinInstructionSize()).Times(1); EXPECT_CALL(isa, predecode(_, _, _, _)).Times(1); EXPECT_CALL(*uop, isBranch()).WillOnce(Return(false)); fetchUnit.tick(); @@ -251,7 +270,8 @@ TEST_F(PipelineFetchUnitTest, fetchTakenBranchMidBlock) { // & a new memory block is requested EXPECT_CALL(memory, getCompletedReads()).Times(0); EXPECT_CALL(memory, clearCompletedReads()).Times(1); - EXPECT_CALL(isa, getMaxInstructionSize()).Times(2); + EXPECT_CALL(isa, getMaxInstructionSize()).Times(1); + EXPECT_CALL(isa, getMinInstructionSize()).Times(1); EXPECT_CALL(isa, predecode(_, _, _, _)).Times(1); EXPECT_CALL(*uop, isBranch()).WillOnce(Return(true)); BranchType bType = BranchType::Unconditional; @@ -266,6 +286,7 @@ TEST_F(PipelineFetchUnitTest, fetchTakenBranchMidBlock) { EXPECT_CALL(memory, getCompletedReads()).Times(1); EXPECT_CALL(memory, clearCompletedReads()).Times(0); EXPECT_CALL(isa, getMaxInstructionSize()).Times(1); + EXPECT_CALL(isa, getMinInstructionSize()).Times(1); EXPECT_CALL(isa, predecode(_, _, _, _)).Times(0); fetchUnit.tick(); @@ -273,12 +294,13 @@ TEST_F(PipelineFetchUnitTest, fetchTakenBranchMidBlock) { // (pred.target) target = {pred.target, blockSize}; EXPECT_CALL(isa, getMaxInstructionSize()).Times(1); + EXPECT_CALL(isa, getMinInstructionSize()).Times(0); EXPECT_CALL(memory, requestRead(target, _)).Times(1); fetchUnit.requestFromPC(); } // Tests the functionality of the supplying from the Loop Buffer -TEST_F(PipelineFetchUnitTest, supplyFromLoopBuffer) { +TEST_P(PipelineFetchUnitTest, supplyFromLoopBuffer) { // Set instructions to be fetched from memory memory::MemoryReadResult memReadResult = { {0x0, blockSize}, RegisterValue(0xFFFF, blockSize), 1}; @@ -367,7 +389,7 @@ TEST_F(PipelineFetchUnitTest, supplyFromLoopBuffer) { // Tests the functionality of idling the supply to the Loop Buffer one of not // taken branch at the loopBoundaryAddress_ -TEST_F(PipelineFetchUnitTest, idleLoopBufferDueToNotTakenBoundary) { +TEST_P(PipelineFetchUnitTest, idleLoopBufferDueToNotTakenBoundary) { // Set instructions to be fetched from memory memory::MemoryReadResult memReadResultA = { {0x0, blockSize}, RegisterValue(0xFFFF, blockSize), 1}; @@ -442,5 +464,288 @@ TEST_F(PipelineFetchUnitTest, idleLoopBufferDueToNotTakenBoundary) { EXPECT_EQ(output.getTailSlots()[0], mOp2); } +// Tests that a min sized instruction held at the end of the fetch buffer is +// allowed to be predecoded in the same cycle as being fetched +TEST_P(PipelineFetchUnitTest, minSizeInstructionAtEndOfBuffer) { + ON_CALL(isa, getMaxInstructionSize()).WillByDefault(Return(insnMaxSizeBytes)); + ON_CALL(isa, getMinInstructionSize()).WillByDefault(Return(insnMinSizeBytes)); + ON_CALL(memory, getCompletedReads()).WillByDefault(Return(completedReads)); + + // Buffer will contain valid min size instruction so predecode returns + // min bytes read + MacroOp mOp = {uopPtr}; + ON_CALL(isa, predecode(_, insnMinSizeBytes, 0x10 - insnMinSizeBytes, _)) + .WillByDefault(DoAll(SetArgReferee<3>(mOp), Return(insnMinSizeBytes))); + + // Fetch the data, only min bytes will be copied to fetch buffer. Should allow + // continuation to predecode + EXPECT_CALL(isa, getMaxInstructionSize()).Times(1); + EXPECT_CALL(memory, getCompletedReads()).Times(1); + EXPECT_CALL(isa, getMinInstructionSize()).Times(1); + EXPECT_CALL(isa, predecode(_, _, _, _)).Times(1); + + uint64_t setPC = blockSize - insnMinSizeBytes; + // Fetch a single minimum sized instruction, buffered bytes = 0 + fetchUnit.updatePC(setPC); + // Tick. Fetch data and predecode + fetchUnit.tick(); + + // Buffer should now be empty as all bytes predecoded + EXPECT_EQ(fetchUnit.bufferedBytes_, 0); + EXPECT_EQ(fetchUnit.output_.getTailSlots()[0], mOp); + + // Expect a block starting at address 16 to be requested when we fetch again + EXPECT_CALL(isa, getMaxInstructionSize()).Times(1); + EXPECT_CALL(memory, + requestRead(Field(&memory::MemoryAccessTarget::address, 16), _)) + .Times(1); + fetchUnit.requestFromPC(); + + // Tick again, expecting that decoding will now resume + MacroOp mOp2 = {uopPtr2}; + memory::MemoryReadResult nextBlockValue = {{16, blockSize}, 0, 1}; + span nextBlock = {&nextBlockValue, 1}; + ON_CALL(memory, getCompletedReads()).WillByDefault(Return(nextBlock)); + ON_CALL(isa, predecode(_, _, _, _)) + .WillByDefault(DoAll(SetArgReferee<3>(mOp2), Return(insnMaxSizeBytes))); + + EXPECT_CALL(isa, getMaxInstructionSize()).Times(1); + // Completed reads called again as more data is requested + EXPECT_CALL(memory, getCompletedReads()).Times(1); + EXPECT_CALL(isa, getMinInstructionSize()).Times(1); + // Output of width 1 so only 1 call to predecode + EXPECT_CALL(isa, predecode(_, _, _, _)).Times(1); + + fetchUnit.tick(); + + // Initially 0 bytes, 16 bytes added, max bytes predecoded leaving (16 - max) + // bytes left + EXPECT_EQ(fetchUnit.bufferedBytes_, 16 - insnMaxSizeBytes); + EXPECT_EQ(fetchUnit.output_.getTailSlots()[0], mOp2); +} + +// Test that invalid min number of bytes held at the end of the buffer is not +// successfully predecoded and that more data is fetched subsequently allowing +// progression as a full instruction is now present in the buffer +TEST_P(PipelineFetchUnitTest, invalidMinBytesAtEndOfBuffer) { + // This is only relevant if min and max size are different. Otherwise, there + // won't be any progression as the fetch unit will be caught in an infinite + // loop + if (insnMinSizeBytes < insnMaxSizeBytes) { + ON_CALL(isa, getMaxInstructionSize()) + .WillByDefault(Return(insnMaxSizeBytes)); + ON_CALL(isa, getMinInstructionSize()) + .WillByDefault(Return(insnMinSizeBytes)); + ON_CALL(memory, getCompletedReads()).WillByDefault(Return(completedReads)); + + // Buffer will contain invalid min bytes so predecode returns 0 bytes read + ON_CALL(isa, predecode(_, insnMinSizeBytes, 0x10 - insnMinSizeBytes, _)) + .WillByDefault(Return(0)); + + // getMaxInstructionSize called for second time in assertion + if (strcmp(SIMENG_BUILD_TYPE, "Release") == 0) { + EXPECT_CALL(isa, getMaxInstructionSize()).Times(1); + } else { + EXPECT_CALL(isa, getMaxInstructionSize()).Times(2); + } + EXPECT_CALL(memory, getCompletedReads()).Times(1); + EXPECT_CALL(isa, getMinInstructionSize()).Times(1); + EXPECT_CALL(isa, predecode(_, _, _, _)).Times(1); + + uint64_t setPC = blockSize - insnMinSizeBytes; + // Fetch a single minimum sized instruction, buffered bytes = 0 + fetchUnit.updatePC(setPC); + // Tick + fetchUnit.tick(); + + // No data consumed + EXPECT_EQ(fetchUnit.bufferedBytes_, insnMinSizeBytes); + EXPECT_EQ(fetchUnit.output_.getTailSlots()[0], MacroOp()); + + // Expect that memory is requested even though there is data in the buffer + // as bufferedBytes < maxInstructionSize + EXPECT_CALL(isa, getMaxInstructionSize()).Times(1); + EXPECT_CALL(memory, + requestRead(Field(&memory::MemoryAccessTarget::address, 16), _)) + .Times(1); + fetchUnit.requestFromPC(); + + // Tick again expecting buffer to be filled and a word is predecoded + MacroOp mOp = {uopPtr}; + memory::MemoryReadResult nextBlockValue = {{16, blockSize}, 0, 1}; + span nextBlock = {&nextBlockValue, 1}; + ON_CALL(memory, getCompletedReads()).WillByDefault(Return(nextBlock)); + ON_CALL(isa, predecode(_, _, _, _)) + .WillByDefault(DoAll(SetArgReferee<3>(mOp), Return(insnMaxSizeBytes))); + + EXPECT_CALL(isa, getMaxInstructionSize()).Times(1); + EXPECT_CALL(memory, getCompletedReads()).Times(1); + EXPECT_CALL(isa, getMinInstructionSize()).Times(1); + EXPECT_CALL(isa, predecode(_, _, _, _)).Times(1); + + fetchUnit.tick(); + + // Initially min bytes, 16 bytes added, max bytes predecoded + EXPECT_EQ(fetchUnit.bufferedBytes_, + (insnMinSizeBytes + 16) - insnMaxSizeBytes); + EXPECT_EQ(fetchUnit.output_.getTailSlots()[0], mOp); + } +} + +// When min and max instruction sizes are different, ensure progression with +// valid min sized instruction at end of buffer when next read doesn't complete. +TEST_P(PipelineFetchUnitTest, validMinSizeReadsDontComplete) { + // In the case that min and max are the same, memory is never requested as + // there is enough data in the buffer. In this case, the test isn't relevant + if (insnMinSizeBytes < insnMaxSizeBytes) { + ON_CALL(isa, getMaxInstructionSize()) + .WillByDefault(Return(insnMaxSizeBytes)); + ON_CALL(isa, getMinInstructionSize()) + .WillByDefault(Return(insnMinSizeBytes)); + ON_CALL(memory, getCompletedReads()).WillByDefault(Return(completedReads)); + + // Buffer will contain valid max and min sized instruction, predecode + // returns max bytes read on first tick + MacroOp mOp = {uopPtr}; + ON_CALL(isa, predecode(_, insnMaxSizeBytes + insnMinSizeBytes, + 0x10 - (insnMaxSizeBytes + insnMinSizeBytes), _)) + .WillByDefault(DoAll(SetArgReferee<3>(mOp), Return(insnMaxSizeBytes))); + + // Fetch the data, only last max + min bytes from block. Should allow + // continuation to predecode + EXPECT_CALL(isa, getMaxInstructionSize()).Times(1); + EXPECT_CALL(memory, getCompletedReads()).Times(1); + EXPECT_CALL(isa, getMinInstructionSize()).Times(1); + EXPECT_CALL(isa, predecode(_, _, _, _)).Times(1); + + uint64_t setPC = blockSize - (insnMaxSizeBytes + insnMinSizeBytes); + // Fetch a minimum and maximum sized instruction, buffered bytes = 0 + fetchUnit.updatePC(setPC); + // Tick and predecode max bytes + fetchUnit.tick(); + + // Ensure max bytes consumed + EXPECT_EQ(fetchUnit.bufferedBytes_, insnMinSizeBytes); + EXPECT_EQ(fetchUnit.pc_, blockSize - insnMinSizeBytes); + EXPECT_EQ(fetchUnit.output_.getTailSlots()[0], mOp); + + // Expect that memory is requested even though there is data in the buffer + // as bufferedBytes < maxInstructionSize + EXPECT_CALL(isa, getMaxInstructionSize()).Times(1); + EXPECT_CALL(memory, + requestRead(Field(&memory::MemoryAccessTarget::address, 16), _)) + .Times(1); + fetchUnit.requestFromPC(); + + EXPECT_EQ(fetchUnit.bufferedBytes_, insnMinSizeBytes); + EXPECT_EQ(fetchUnit.pc_, blockSize - insnMinSizeBytes); + + // Memory doesn't complete reads in next cycle but buffered bytes should be + // predecoded + MacroOp mOp2 = {uopPtr2}; + ON_CALL(memory, getCompletedReads()) + .WillByDefault(Return(span{nullptr, 0})); + ON_CALL(isa, predecode(_, insnMinSizeBytes, 0x10 - insnMinSizeBytes, _)) + .WillByDefault(DoAll(SetArgReferee<3>(mOp2), Return(insnMinSizeBytes))); + + // Path through fetch as follows: + // More data required as bufferedBytes_ < maxInsnSize so getCompletedReads + // Doesn't complete so buffer doesn't get added to + // Buffer still has some valid data so predecode should be called + + EXPECT_CALL(isa, getMaxInstructionSize()).Times(1); + EXPECT_CALL(memory, getCompletedReads()).Times(1); + EXPECT_CALL(isa, getMinInstructionSize()).Times(2); + EXPECT_CALL(isa, predecode(_, _, _, _)).Times(1); + + // Tick + fetchUnit.tick(); + + // Ensure min bytes are consumed + EXPECT_EQ(fetchUnit.bufferedBytes_, 0); + EXPECT_EQ(fetchUnit.pc_, 16); + EXPECT_EQ(fetchUnit.output_.getTailSlots()[0], mOp2); + } +} + +// Test that minimum bytes held at the end of the buffer is not successfully +// predecoded and should be re-tried when reads don't complete +TEST_P(PipelineFetchUnitTest, invalidMinBytesreadsDontComplete) { + // In the case where min and max are the same, predecode will never return 0 + // so the test is only relevent in the case where they are different + if (insnMinSizeBytes < insnMaxSizeBytes) { + ON_CALL(isa, getMaxInstructionSize()) + .WillByDefault(Return(insnMaxSizeBytes)); + ON_CALL(isa, getMinInstructionSize()) + .WillByDefault(Return(insnMinSizeBytes)); + ON_CALL(memory, getCompletedReads()).WillByDefault(Return(completedReads)); + + // Buffer will contain invalid min bytes so predecode returns 0 bytes read + ON_CALL(isa, predecode(_, insnMinSizeBytes, 0x10 - insnMinSizeBytes, _)) + .WillByDefault(Return(0)); + + // getMaxInstructionSize called for second time in assertion + if (strcmp(SIMENG_BUILD_TYPE, "Release") == 0) { + EXPECT_CALL(isa, getMaxInstructionSize()).Times(1); + } else { + EXPECT_CALL(isa, getMaxInstructionSize()).Times(2); + } + EXPECT_CALL(memory, getCompletedReads()).Times(1); + EXPECT_CALL(isa, getMinInstructionSize()).Times(1); + EXPECT_CALL(isa, predecode(_, _, _, _)).Times(1); + + uint64_t setPC = blockSize - insnMinSizeBytes; + // Fetch a minimum number of bytes, buffered bytes = 0 + fetchUnit.updatePC(setPC); + // Tick + fetchUnit.tick(); + + // No data consumed + EXPECT_EQ(fetchUnit.bufferedBytes_, insnMinSizeBytes); + EXPECT_EQ(fetchUnit.pc_, blockSize - insnMinSizeBytes); + EXPECT_EQ(fetchUnit.output_.getTailSlots()[0], MacroOp()); + + // Expect that memory is requested even though there is data in the buffer + // as bufferedBytes < maxInstructionSize + EXPECT_CALL(isa, getMaxInstructionSize()).Times(1); + EXPECT_CALL(memory, + requestRead(Field(&memory::MemoryAccessTarget::address, 16), _)) + .Times(1); + fetchUnit.requestFromPC(); + + EXPECT_EQ(fetchUnit.bufferedBytes_, insnMinSizeBytes); + EXPECT_EQ(fetchUnit.pc_, blockSize - insnMinSizeBytes); + + // Memory doesn't complete reads in next cycle but buffered bytes should + // attempt to be predecoded + ON_CALL(memory, getCompletedReads()) + .WillByDefault(Return(span{nullptr, 0})); + // Predecode still returns no bytes read + ON_CALL(isa, predecode(_, insnMinSizeBytes, 0x10 - insnMinSizeBytes, _)) + .WillByDefault(Return(0)); + + // getMaxInsnSize called again in assertion + if (strcmp(SIMENG_BUILD_TYPE, "Release") == 0) { + EXPECT_CALL(isa, getMaxInstructionSize()).Times(1); + } else { + EXPECT_CALL(isa, getMaxInstructionSize()).Times(2); + } + EXPECT_CALL(memory, getCompletedReads()).Times(1); + EXPECT_CALL(isa, getMinInstructionSize()).Times(2); + EXPECT_CALL(isa, predecode(_, _, _, _)).Times(1); + + // Tick + fetchUnit.tick(); + + // Ensure min bytes are not consumed + EXPECT_EQ(fetchUnit.bufferedBytes_, insnMinSizeBytes); + EXPECT_EQ(fetchUnit.pc_, blockSize - insnMinSizeBytes); + EXPECT_EQ(fetchUnit.output_.getTailSlots()[0], MacroOp()); + } +} + +INSTANTIATE_TEST_SUITE_P(PipelineFetchUnitTests, PipelineFetchUnitTest, + ::testing::Values(std::pair(2, 4), std::pair(4, 4))); + } // namespace pipeline } // namespace simeng diff --git a/test/unit/riscv/ArchitectureTest.cc b/test/unit/riscv/ArchitectureTest.cc index abcbeb91a6..8364dbe692 100644 --- a/test/unit/riscv/ArchitectureTest.cc +++ b/test/unit/riscv/ArchitectureTest.cc @@ -62,7 +62,7 @@ class RiscVArchitectureTest : public testing::Test { // addi sp, ra, 2000 std::array validInstrBytes = {0x13, 0x81, 0x00, 0x7d}; - std::array invalidInstrBytes = {0x7d, 0x00, 0x81, 0xbb}; + std::array invalidInstrBytes = {0x7f, 0x00, 0x81, 0xbb}; std::unique_ptr arch; kernel::Linux kernel;