Add 64bit zfhmin semantic functions PiperOrigin-RevId: 752452472 Change-Id: I7e6222c30d3431259ac1c98f2b1df8659bf0268c
diff --git a/riscv/riscv_zfh.isa b/riscv/riscv_zfh.isa index 6ba21c6..9afe769 100644 --- a/riscv/riscv_zfh.isa +++ b/riscv/riscv_zfh.isa
@@ -84,3 +84,28 @@ } } +slot riscv64_zfh_min: riscv_zfh_min { + includes { + #include "riscv/riscv_i_instructions.h" + #include "riscv/riscv_zfh_instructions.h" + } + default size = 4; + default latency = 0; + default opcode = + disasm: "Unimplemented instruction at 0x%(@:08x)", + semfunc: "&RV32VUnimplementedInstruction"; + opcodes { + flh{(: rs1, I_imm12 : ), (: : frd)}, + resources: {next_pc, rs1 : frd[0..]}, + semfunc: "&RV64::RiscVILhu", "&RV64::RiscVZfhFlhChild", // new + disasm: "flh", "%frd, %I_imm12(%rs1)"; + fsh{: rs1, S_imm12, frs2}, + resources: {next_pc, rs1, frs2}, + semfunc: "&RV64::RiscVISh", // new + disasm: "fsh", "%frs2, %S_imm12(%rs1)"; + fmv_xh{: frs1 : rd}, + resources: {next_pc, frs1 : rd[0..]}, + semfunc: "&RV64::RiscVZfhFMvxh", // new + disasm: "fmv.x.h", "%rd, %frs1"; + } +}
diff --git a/riscv/riscv_zfh_instructions.cc b/riscv/riscv_zfh_instructions.cc index 34ce19d..8f20a82 100644 --- a/riscv/riscv_zfh_instructions.cc +++ b/riscv/riscv_zfh_instructions.cc
@@ -147,6 +147,21 @@ } // namespace RV32 +namespace RV64 { +// Move a half precision value from a float register to a 32 bit integer +// register. +void RiscVZfhFMvxh(const Instruction *instruction) { + RiscVUnaryFloatOp<uint64_t, HalfFP>(instruction, [](HalfFP a) -> uint64_t { + if (FPTypeInfo<HalfFP>::SignBit(a)) { + // Repeat the sign bit for negative values. + return 0xFFFF'FFFF'FFFF'0000 | a.value; + } + return static_cast<uint64_t>(a.value); + }); +} + +} // namespace RV64 + void RiscVZfhFlhChild(const Instruction *instruction) { using FPUInt = typename FPTypeInfo<HalfFP>::UIntType; LoadContext *context = static_cast<LoadContext *>(instruction->context());
diff --git a/riscv/riscv_zfh_instructions.h b/riscv/riscv_zfh_instructions.h index 536dbc3..9181490 100644 --- a/riscv/riscv_zfh_instructions.h +++ b/riscv/riscv_zfh_instructions.h
@@ -29,17 +29,64 @@ using HalfFP = ::mpact::sim::generic::HalfFP; namespace RV32 { +// Source Operands: +// frs1: Float Register +// Destination Operands: +// rd: Integer Register void RiscVZfhFMvxh(const Instruction *instruction); } // namespace RV32 -namespace RV64 {} // namespace RV64 +namespace RV64 { +// Source Operands: +// frs1: Float Register +// Destination Operands: +// rd: Integer Register +void RiscVZfhFMvxh(const Instruction *instruction); +} // namespace RV64 +// Source Operands: *none* +// Destination Operands: +// frd: Float Register void RiscVZfhFlhChild(const Instruction *instruction); + +// Source Operands: +// rs1: Integer Register +// Destination Operands: +// frd: Float Register void RiscVZfhFMvhx(const Instruction *instruction); + +// Source Operands: +// frs1: Float Register +// rm: Literal Operand (rounding mode) +// Destination Operands: +// frd: Float Register +// fflags: Accrued Exception Flags field in FCSR void RiscVZfhCvtSh(const Instruction *instruction); + +// Source Operands: +// frs1: Float Register +// rm: Literal Operand (rounding mode) +// Destination Operands: +// frd: Float Register +// fflags: Accrued Exception Flags field in FCSR void RiscVZfhCvtHs(const Instruction *instruction); + +// Source Operands: +// frs1: Float Register +// rm: Literal Operand (rounding mode) +// Destination Operands: +// frd: Float Register +// fflags: Accrued Exception Flags field in FCSR void RiscVZfhCvtDh(const Instruction *instruction); + +// Source Operands: +// frs1: Float Register +// rm: Literal Operand (rounding mode) +// Destination Operands: +// frd: Float Register +// fflags: Accrued Exception Flags field in FCSR void RiscVZfhCvtHd(const Instruction *instruction); + // TODO(b/409778536): Factor out generic unimplemented instruction semantic // function. void RV32VUnimplementedInstruction(const Instruction *instruction);
diff --git a/riscv/test/riscv_zfh_instructions_test.cc b/riscv/test/riscv_zfh_instructions_test.cc index 1aaa685..8b02448 100644 --- a/riscv/test/riscv_zfh_instructions_test.cc +++ b/riscv/test/riscv_zfh_instructions_test.cc
@@ -38,6 +38,9 @@ #include "riscv/riscv_register.h" #include "riscv/test/riscv_fp_test_base.h" +// This file contains the unit tests for the RiscV ZFH extension instructions. +// Testing is focused on the instruction semantic functions. + namespace { using ::mpact::sim::generic::operator*; // NOLINT: is used below. @@ -53,6 +56,7 @@ using ::mpact::sim::riscv::RiscVZfhFlhChild; using ::mpact::sim::riscv::RiscVZfhFMvhx; using ::mpact::sim::riscv::RV32Register; +using ::mpact::sim::riscv::RV64Register; using ::mpact::sim::riscv::RVFpRegister; using ::mpact::sim::riscv::RV32::RiscVILhu; using ::mpact::sim::riscv::RV32::RiscVZfhFMvxh; @@ -67,6 +71,50 @@ const int kRoundingModeRoundDown = static_cast<int>(FPRoundingMode::kRoundDown); const int kRoundingModeRoundUp = static_cast<int>(FPRoundingMode::kRoundUp); +class RVZfhInstructionTestBase : public RiscVFPInstructionTestBase { + protected: + template <typename AddressType, typename ValueType> + void SetupMemory(AddressType, ValueType); + + template <typename ReturnType, typename IntegerRegister> + ReturnType LoadHalfHelper(typename IntegerRegister::ValueType, int16_t); +}; + +template <typename AddressType, typename ValueType> +void RVZfhInstructionTestBase::SetupMemory(AddressType address, + ValueType value) { + DataBuffer *mem_db = state_->db_factory()->Allocate<ValueType>(1); + mem_db->Set<ValueType>(0, value); + state_->StoreMemory(instruction_, address, mem_db); + mem_db->DecRef(); +} + +template <typename ReturnType, typename IntegerRegister> +ReturnType RVZfhInstructionTestBase::LoadHalfHelper( + typename IntegerRegister::ValueType base, int16_t offset) { + // Technically the offset for FL* is a 12 bit signed integer but we'll use 16 + // bits for testing. + const std::string kRs1Name("x1"); + const std::string kFrdName("f5"); + AppendRegisterOperands<IntegerRegister>({kRs1Name}, {}); + AppendRegisterOperands<RVFpRegister>(child_instruction_, {}, {kFrdName}); + + ImmediateOperand<int16_t> *offset_source_operand = + new ImmediateOperand<int16_t>(offset); + instruction_->AppendSource(offset_source_operand); + + SetRegisterValues<typename IntegerRegister::ValueType, IntegerRegister>( + {{kRs1Name, static_cast<IntegerRegister::ValueType>(base)}}); + SetRegisterValues<uint64_t, RVFpRegister>({{kFrdName, 0}}); + + instruction_->Execute(nullptr); + + ReturnType observed_val = state_->GetRegister<RVFpRegister>(kFrdName) + .first->data_buffer() + ->template Get<ReturnType>(0); + return observed_val; +} + // A source operand that is used to set the rounding mode. This is less // confusing than using a register source operand since the rounding mode is // part of the instruction encoding. @@ -104,7 +152,7 @@ FPRoundingMode rounding_mode_; }; -class RV32ZfhInstructionTest : public RiscVFPInstructionTestBase { +class RV32ZfhInstructionTest : public RVZfhInstructionTestBase { protected: // Test conversion instructions. The instance variable semantic_function_ is // used to set the semantic function for the instruction and should be set @@ -146,12 +194,6 @@ template <FPRoundingMode rm> void RoundingPointTest(uint16_t); - template <typename T> - void SetupMemory(uint64_t, T); - - template <typename T, typename IntegerRegister> - T LoadHalfHelper(uint64_t, int16_t); - Instruction::SemanticFunction semantic_function_ = nullptr; }; @@ -191,37 +233,6 @@ << " with rounding mode: " << static_cast<int>(rm); } -template <typename T> -void RV32ZfhInstructionTest::SetupMemory(uint64_t address, T value) { - DataBuffer *mem_db = state_->db_factory()->Allocate<T>(1); - mem_db->Set<T>(0, value); - state_->StoreMemory(instruction_, address, mem_db); - mem_db->DecRef(); -} - -template <typename T, typename IntegerRegister> -T RV32ZfhInstructionTest::LoadHalfHelper(uint64_t base, int16_t offset) { - const std::string kRs1Name("x1"); - const std::string kFrdName("f5"); - AppendRegisterOperands<IntegerRegister>({kRs1Name}, {}); - AppendRegisterOperands<RVFpRegister>(child_instruction_, {}, {kFrdName}); - - ImmediateOperand<int16_t> *offset_source_operand = - new ImmediateOperand<int16_t>(offset); - instruction_->AppendSource(offset_source_operand); - - SetRegisterValues<typename IntegerRegister::ValueType, IntegerRegister>( - {{kRs1Name, static_cast<IntegerRegister::ValueType>(base)}}); - SetRegisterValues<uint32_t, RVFpRegister>({{kFrdName, 0}}); - - instruction_->Execute(nullptr); - - T observed_val = state_->GetRegister<RVFpRegister>(kFrdName) - .first->data_buffer() - ->template Get<T>(0); - return observed_val; -} - // Test the FP16 load instruction. The semantic functions should match the isa // file. TEST_F(RV32ZfhInstructionTest, RiscVFlh) { @@ -229,7 +240,7 @@ SetChildInstruction(); SetChildSemanticFunction(&RiscVZfhFlhChild); - SetupMemory<uint16_t>(0xFF, 0xBEEF); + SetupMemory<uint32_t, uint16_t>(0xFF, 0xBEEF); HalfFP observed_val = LoadHalfHelper<HalfFP, RV32Register>(/* base */ 0x0, /* offset */ 0xFF); @@ -243,7 +254,7 @@ SetChildInstruction(); SetChildSemanticFunction(&RiscVZfhFlhChild); - SetupMemory<uint16_t>(0xFF, 0xBEEF); + SetupMemory<uint32_t, uint16_t>(0xFF, 0xBEEF); float observed_val = LoadHalfHelper<float, RV32Register>(/* base */ 0xFF, /* offset */ 0); @@ -257,7 +268,7 @@ SetChildInstruction(); SetChildSemanticFunction(&RiscVZfhFlhChild); - SetupMemory<uint16_t>(0xFF, 0xBEEF); + SetupMemory<uint32_t, uint16_t>(0xFF, 0xBEEF); double observed_val = LoadHalfHelper<double, RV32Register>( /* base */ 0x0100, /* offset */ -1); @@ -553,4 +564,62 @@ EXPECT_EQ(absl::bit_cast<uint16_t>(actual_n0), expected_n0); } +class RV64ZfhInstructionTest : public RVZfhInstructionTestBase {}; + +// Move half precision from a float register to an integer register. The IEEE754 +// encoding is preserved in the integer register. +TEST_F(RV64ZfhInstructionTest, RiscVZfhFMvxh) { + SetSemanticFunction(&mpact::sim::riscv::RV64::RiscVZfhFMvxh); + UnaryOpFPTestHelper<uint64_t, HalfFP>( + "fmv.x.h", instruction_, {"f", "x"}, 32, [](HalfFP half_fp) -> uint64_t { + bool sign = 1 & (half_fp.value >> (FPTypeInfo<HalfFP>::kBitSize - 1)); + // Fill the upper XLEN-16 bits with the sign bit as per the spec. + uint64_t result = sign ? 0xFFFF'FFFF'FFFF'0000 : 0; + result |= static_cast<uint64_t>(half_fp.value); + return result; + }); +} + +// Test the FP16 load instruction. The semantic functions should match the isa +// file. +TEST_F(RV64ZfhInstructionTest, RiscVFlh) { + SetSemanticFunction(&RiscVILhu); + SetChildInstruction(); + SetChildSemanticFunction(&RiscVZfhFlhChild); + + SetupMemory<uint64_t, uint16_t>(0xFF, 0xBEEF); + + HalfFP observed_val = + LoadHalfHelper<HalfFP, RV64Register>(/* base */ 0x0, /* offset */ 0xFF); + EXPECT_EQ(observed_val.value, 0xBEEF); +} + +// Test the FP16 load instruction. When looking at the register contents as a +// float, it should be NaN. +TEST_F(RV64ZfhInstructionTest, RiscVFlh_float_nanbox) { + SetSemanticFunction(&RiscVILhu); + SetChildInstruction(); + SetChildSemanticFunction(&RiscVZfhFlhChild); + + SetupMemory<uint64_t, uint16_t>(0xFF, 0xBEEF); + + float observed_val = + LoadHalfHelper<float, RV64Register>(/* base */ 0xFF, /* offset */ 0); + EXPECT_TRUE(std::isnan(observed_val)); +} + +// Test the FP16 load instruction. When looking at the register contents as a +// double, it should be NaN. +TEST_F(RV64ZfhInstructionTest, RiscVFlh_double_nanbox) { + SetSemanticFunction(&RiscVILhu); + SetChildInstruction(); + SetChildSemanticFunction(&RiscVZfhFlhChild); + + SetupMemory<uint64_t, uint16_t>(0xFF, 0xBEEF); + + double observed_val = LoadHalfHelper<double, RV64Register>( + /* base */ 0x0100, /* offset */ -1); + EXPECT_TRUE(std::isnan(observed_val)); +} + } // namespace