| // Copyright 2025 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "riscv/riscv_zfh_instructions.h" |
| |
| #include <sys/types.h> |
| |
| #include <algorithm> |
| #include <any> |
| #include <cassert> |
| #include <cmath> |
| #include <cstdint> |
| #include <functional> |
| #include <ios> |
| #include <limits> |
| #include <string> |
| #include <tuple> |
| #include <type_traits> |
| #include <vector> |
| |
| #include "absl/base/casts.h" |
| #include "absl/random/distributions.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/string_view.h" |
| #include "absl/types/span.h" |
| #include "googlemock/include/gmock/gmock.h" |
| #include "mpact/sim/generic/data_buffer.h" |
| #include "mpact/sim/generic/immediate_operand.h" |
| #include "mpact/sim/generic/instruction.h" |
| #include "mpact/sim/generic/operand_interface.h" |
| #include "mpact/sim/generic/type_helpers.h" |
| #include "riscv/riscv_fp_host.h" |
| #include "riscv/riscv_fp_info.h" |
| #include "riscv/riscv_i_instructions.h" |
| #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. |
| using ::mpact::sim::generic::DataBuffer; |
| using ::mpact::sim::generic::HalfFP; |
| using ::mpact::sim::generic::ImmediateOperand; |
| using ::mpact::sim::generic::Instruction; |
| using ::mpact::sim::riscv::FPExceptions; |
| using ::mpact::sim::riscv::FPRoundingMode; |
| using ::mpact::sim::riscv::RiscVZfhCvtDh; |
| using ::mpact::sim::riscv::RiscVZfhCvtHd; |
| using ::mpact::sim::riscv::RiscVZfhCvtHs; |
| using ::mpact::sim::riscv::RiscVZfhCvtHw; |
| using ::mpact::sim::riscv::RiscVZfhCvtHwu; |
| using ::mpact::sim::riscv::RiscVZfhCvtSh; |
| using ::mpact::sim::riscv::RiscVZfhFadd; |
| using ::mpact::sim::riscv::RiscVZfhFdiv; |
| using ::mpact::sim::riscv::RiscVZfhFlhChild; |
| using ::mpact::sim::riscv::RiscVZfhFmadd; |
| using ::mpact::sim::riscv::RiscVZfhFmax; |
| using ::mpact::sim::riscv::RiscVZfhFmin; |
| using ::mpact::sim::riscv::RiscVZfhFmsub; |
| using ::mpact::sim::riscv::RiscVZfhFmul; |
| using ::mpact::sim::riscv::RiscVZfhFnmadd; |
| using ::mpact::sim::riscv::RiscVZfhFnmsub; |
| using ::mpact::sim::riscv::RiscVZfhFsgnj; |
| using ::mpact::sim::riscv::RiscVZfhFsgnjn; |
| using ::mpact::sim::riscv::RiscVZfhFsgnjx; |
| using ::mpact::sim::riscv::RiscVZfhFsqrt; |
| using ::mpact::sim::riscv::RiscVZfhFsub; |
| using ::mpact::sim::riscv::RV32Register; |
| using ::mpact::sim::riscv::RV64Register; |
| using ::mpact::sim::riscv::RVFpRegister; |
| using ::mpact::sim::riscv::ScopedFPRoundingMode; |
| using ::mpact::sim::riscv::ScopedFPStatus; |
| using ::mpact::sim::riscv::RV64::RiscVZfhCvtHl; |
| using ::mpact::sim::riscv::RV64::RiscVZfhCvtHlu; |
| using ::mpact::sim::riscv::RV64::RiscVZfhCvtLh; |
| using ::mpact::sim::riscv::RV64::RiscVZfhCvtLuh; |
| |
| using ::mpact::sim::riscv::test::FloatingPointToString; |
| using ::mpact::sim::riscv::test::FPCompare; |
| using ::mpact::sim::riscv::test::FpConversionsTestHelper; |
| using ::mpact::sim::riscv::test::FPTypeInfo; |
| using ::mpact::sim::riscv::test::kTestValueLength; |
| using ::mpact::sim::riscv::test::RiscVFPInstructionTestBase; |
| |
| const int kRoundingModeRoundToNearest = |
| static_cast<int>(FPRoundingMode::kRoundToNearest); |
| const int kRoundingModeRoundTowardsZero = |
| static_cast<int>(FPRoundingMode::kRoundTowardsZero); |
| const int kRoundingModeRoundDown = static_cast<int>(FPRoundingMode::kRoundDown); |
| const int kRoundingModeRoundUp = static_cast<int>(FPRoundingMode::kRoundUp); |
| |
| // 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. |
| class TestRoundingModeSourceOperand |
| : public mpact::sim::generic::SourceOperandInterface { |
| public: |
| explicit TestRoundingModeSourceOperand() |
| : rounding_mode_(FPRoundingMode::kRoundToNearest) {} |
| |
| void SetRoundingMode(FPRoundingMode rounding_mode) { |
| rounding_mode_ = rounding_mode; |
| } |
| |
| bool AsBool(int) override { return static_cast<bool>(rounding_mode_); } |
| int8_t AsInt8(int) override { return static_cast<int8_t>(rounding_mode_); } |
| uint8_t AsUint8(int) override { return static_cast<uint8_t>(rounding_mode_); } |
| int16_t AsInt16(int) override { return static_cast<int16_t>(rounding_mode_); } |
| uint16_t AsUint16(int) override { |
| return static_cast<uint16_t>(rounding_mode_); |
| } |
| int32_t AsInt32(int) override { return static_cast<int32_t>(rounding_mode_); } |
| uint32_t AsUint32(int) override { |
| return static_cast<uint32_t>(rounding_mode_); |
| } |
| int64_t AsInt64(int) override { return static_cast<int64_t>(rounding_mode_); } |
| uint64_t AsUint64(int) override { |
| return static_cast<uint64_t>(rounding_mode_); |
| } |
| |
| std::vector<int> shape() const override { return {1}; } |
| std::string AsString() const override { return std::string(""); } |
| std::any GetObject() const override { return std::any(); } |
| |
| protected: |
| FPRoundingMode rounding_mode_; |
| }; |
| |
| template <typename XRegister> |
| class RVZfhInstructionTestBase : public RiscVFPInstructionTestBase<XRegister> { |
| protected: |
| template <typename AddressType, typename ValueType> |
| void SetupMemory(AddressType, ValueType); |
| |
| template <typename ReturnType> |
| ReturnType LoadHalfHelper(typename XRegister::ValueType, int16_t); |
| |
| template <typename DestRegisterType, typename LhsRegisterType, typename R, |
| typename LHS> |
| void UnaryOpWithFflagsMixedTestHelper( |
| absl::string_view name, Instruction *inst, |
| absl::Span<const absl::string_view> reg_prefixes, int delta_position, |
| std::function<std::tuple<R, uint32_t>(LHS, uint32_t)> operation); |
| |
| template <typename R, typename LHS, typename MHS, typename RHS> |
| void TernaryOpWithFflagsFPTestHelper( |
| absl::string_view name, Instruction *inst, |
| absl::Span<const absl::string_view> reg_prefixes, int delta_position, |
| std::function<std::tuple<R, uint32_t>(LHS, MHS, RHS)> operation); |
| |
| uint32_t GetOperationFlags(std::function<void(void)> operation); |
| |
| template <typename FPType> |
| void FmvHxNanBoxHelper(); |
| |
| void FmvXhHelper(); |
| |
| template <typename SourceIntegerType> |
| void CvtHwHelper(absl::string_view); |
| |
| template <typename DestinationIntegerType> |
| void CvtWhHelper(absl::string_view); |
| |
| void CmpEqHelper(); |
| |
| void CmpLtHelper(); |
| |
| void CmpLeHelper(); |
| |
| void ClassHelper(); |
| }; |
| |
| template <typename XRegister> |
| template <typename AddressType, typename ValueType> |
| void RVZfhInstructionTestBase<XRegister>::SetupMemory(AddressType address, |
| ValueType value) { |
| DataBuffer *mem_db = |
| this->state_->db_factory()->template Allocate<ValueType>(1); |
| mem_db->Set<ValueType>(0, value); |
| this->state_->StoreMemory(this->instruction_, address, mem_db); |
| mem_db->DecRef(); |
| } |
| |
| template <typename XRegister> |
| template <typename ReturnType> |
| ReturnType RVZfhInstructionTestBase<XRegister>::LoadHalfHelper( |
| typename XRegister::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"); |
| this->template AppendRegisterOperands<XRegister>({kRs1Name}, {}); |
| this->template AppendRegisterOperands<RVFpRegister>(this->child_instruction_, |
| {}, {kFrdName}); |
| |
| ImmediateOperand<int16_t> *offset_source_operand = |
| new ImmediateOperand<int16_t>(offset); |
| this->instruction_->AppendSource(offset_source_operand); |
| |
| this->template SetRegisterValues<typename XRegister::ValueType, XRegister>( |
| {{kRs1Name, static_cast<XRegister::ValueType>(base)}}); |
| this->template SetRegisterValues<RVFpRegister::ValueType, RVFpRegister>( |
| {{kFrdName, 0}}); |
| |
| this->instruction_->Execute(nullptr); |
| |
| ReturnType observed_val = |
| this->state_->template GetRegister<RVFpRegister>(kFrdName) |
| .first->data_buffer() |
| ->template Get<ReturnType>(0); |
| return observed_val; |
| } |
| |
| // Helper for statements that set the host FPU flags. This is used to |
| // determine the flags set by an operation. Enables targeted capture of flags |
| // that are set by an operation. The Simulation flags are restored to the |
| // initial state after the operation is executed. |
| template <typename XRegister> |
| uint32_t RVZfhInstructionTestBase<XRegister>::GetOperationFlags( |
| std::function<void(void)> operation) { |
| uint32_t initial_fflags = this->rv_fp_->fflags()->GetUint32(); |
| uint32_t delta_fflags = 0; |
| this->rv_fp_->fflags()->Write(static_cast<uint32_t>(0)); |
| { |
| ScopedFPStatus sfpstatus(this->rv_fp_->host_fp_interface(), |
| this->rv_fp_->GetRoundingMode()); |
| operation(); |
| } |
| delta_fflags = this->rv_fp_->fflags()->GetUint32(); |
| this->rv_fp_->fflags()->Write(initial_fflags); |
| return delta_fflags; |
| } |
| |
| // Helper for unary instructions that go between floats and integers. |
| // TODO(b/419352093): Change the rounding mode datatype to int. |
| template <typename XRegister> |
| template <typename DestRegisterType, typename LhsRegisterType, typename R, |
| typename LHS> |
| void RVZfhInstructionTestBase<XRegister>::UnaryOpWithFflagsMixedTestHelper( |
| absl::string_view name, Instruction *inst, |
| absl::Span<const absl::string_view> reg_prefixes, int delta_position, |
| std::function<std::tuple<R, uint32_t>(LHS, uint32_t)> operation) { |
| using LhsInt = typename FPTypeInfo<LHS>::IntType; |
| using RInt = typename FPTypeInfo<R>::IntType; |
| LHS lhs_values[kTestValueLength]; |
| auto lhs_span = absl::Span<LHS>(lhs_values); |
| const std::string kR1Name = absl::StrCat(reg_prefixes[0], 1); |
| const std::string kRdName = absl::StrCat(reg_prefixes[1], 5); |
| // This is used for the rounding mode operand. |
| const std::string kRmName = absl::StrCat("x", 10); |
| if (kR1Name[0] == 'x') { |
| this->template AppendRegisterOperands<XRegister>({kR1Name}, {}); |
| } else { |
| this->template AppendRegisterOperands<RVFpRegister>({kR1Name}, {}); |
| } |
| if (kRdName[0] == 'x') { |
| this->template AppendRegisterOperands<XRegister>({}, {kRdName}); |
| } else { |
| this->template AppendRegisterOperands<RVFpRegister>({}, {kRdName}); |
| } |
| this->template AppendRegisterOperands<XRegister>({kRmName}, {}); |
| auto *flag_op = |
| this->rv_fp_->fflags()->CreateSetDestinationOperand(0, "fflags"); |
| this->instruction_->AppendDestination(flag_op); |
| if constexpr (std::is_integral<LHS>::value) { |
| for (auto &lhs : lhs_span) { |
| lhs = absl::Uniform(absl::IntervalClosed, this->bitgen_, |
| std::numeric_limits<LHS>::min(), |
| std::numeric_limits<LHS>::max()); |
| } |
| *reinterpret_cast<LHS *>(&lhs_span[0]) = 0; |
| *reinterpret_cast<LHS *>(&lhs_span[1]) = 1; |
| *reinterpret_cast<LHS *>(&lhs_span[2]) = 2; |
| *reinterpret_cast<LHS *>(&lhs_span[3]) = 4; |
| *reinterpret_cast<LHS *>(&lhs_span[4]) = 8; |
| *reinterpret_cast<LHS *>(&lhs_span[5]) = 16; |
| *reinterpret_cast<LHS *>(&lhs_span[6]) = 1024; |
| *reinterpret_cast<LHS *>(&lhs_span[7]) = 65000; |
| } else { |
| this->template FillArrayWithRandomFPValues<LHS>(lhs_span); |
| *reinterpret_cast<LhsInt *>(&lhs_span[0]) = FPTypeInfo<LHS>::kQNaN; |
| *reinterpret_cast<LhsInt *>(&lhs_span[1]) = FPTypeInfo<LHS>::kSNaN; |
| *reinterpret_cast<LhsInt *>(&lhs_span[2]) = FPTypeInfo<LHS>::kPosInf; |
| *reinterpret_cast<LhsInt *>(&lhs_span[3]) = FPTypeInfo<LHS>::kNegInf; |
| *reinterpret_cast<LhsInt *>(&lhs_span[4]) = FPTypeInfo<LHS>::kPosZero; |
| *reinterpret_cast<LhsInt *>(&lhs_span[5]) = FPTypeInfo<LHS>::kNegZero; |
| *reinterpret_cast<LhsInt *>(&lhs_span[6]) = FPTypeInfo<LHS>::kPosDenorm; |
| *reinterpret_cast<LhsInt *>(&lhs_span[7]) = FPTypeInfo<LHS>::kNegDenorm; |
| } |
| for (int i = 0; i < kTestValueLength; i++) { |
| if constexpr (std::is_integral<LHS>::value) { |
| this->template SetRegisterValues<LHS, LhsRegisterType>( |
| {{kR1Name, lhs_span[i]}}); |
| } else { |
| this->template SetNaNBoxedRegisterValues<LHS, LhsRegisterType>( |
| {{kR1Name, lhs_span[i]}}); |
| } |
| |
| for (int rm : {0, 1, 2, 3, 4}) { |
| this->rv_fp_->SetRoundingMode(static_cast<FPRoundingMode>(rm)); |
| this->rv_fp_->fflags()->Write(static_cast<uint32_t>(0)); |
| this->template SetRegisterValues<int, XRegister>({{kRmName, rm}, {}}); |
| this->template SetRegisterValues<typename DestRegisterType::ValueType, |
| DestRegisterType>({{kRdName, 0}}); |
| |
| inst->Execute(nullptr); |
| auto instruction_fflags = this->rv_fp_->fflags()->GetUint32(); |
| |
| R op_val; |
| uint32_t test_operation_fflags; |
| { |
| ScopedFPRoundingMode scoped_rm(this->rv_fp_->host_fp_interface(), rm); |
| std::tie(op_val, test_operation_fflags) = operation(lhs_span[i], rm); |
| } |
| |
| auto reg_val = |
| this->state_->template GetRegister<DestRegisterType>(kRdName) |
| .first->data_buffer() |
| ->template Get<R>(0); |
| FPCompare<R>( |
| op_val, reg_val, delta_position, |
| absl::StrCat(name, " ", i, ": ", |
| FloatingPointToString<LHS>(lhs_span[i]), " rm: ", rm)); |
| LhsInt lhs_uint = absl::bit_cast<LhsInt>(lhs_span[i]); |
| RInt op_val_uint = absl::bit_cast<RInt>(op_val); |
| EXPECT_EQ(test_operation_fflags, instruction_fflags) |
| << name << "(" << FloatingPointToString<LHS>(lhs_span[i]) << ") " |
| << std::hex << name << "(0x" << lhs_uint |
| << ") == " << FloatingPointToString<R>(op_val) << std::hex << " 0x" |
| << op_val_uint << " rm: " << rm; |
| } |
| } |
| } |
| |
| template <typename XRegister> |
| template <typename R, typename LHS, typename MHS, typename RHS> |
| void RVZfhInstructionTestBase<XRegister>::TernaryOpWithFflagsFPTestHelper( |
| absl::string_view name, Instruction *inst, |
| absl::Span<const absl::string_view> reg_prefixes, int delta_position, |
| std::function<std::tuple<R, uint32_t>(LHS, MHS, RHS)> operation) { |
| using LhsRegisterType = RVFpRegister; |
| using MhsRegisterType = RVFpRegister; |
| using RhsRegisterType = RVFpRegister; |
| using DestRegisterType = RVFpRegister; |
| LHS lhs_values[kTestValueLength]; |
| MHS mhs_values[kTestValueLength]; |
| RHS rhs_values[kTestValueLength]; |
| auto lhs_span = absl::Span<LHS>(lhs_values); |
| auto mhs_span = absl::Span<MHS>(mhs_values); |
| auto rhs_span = absl::Span<RHS>(rhs_values); |
| const std::string kR1Name = absl::StrCat(reg_prefixes[0], 1); |
| const std::string kR2Name = absl::StrCat(reg_prefixes[1], 2); |
| const std::string kR3Name = absl::StrCat(reg_prefixes[2], 3); |
| const std::string kRdName = absl::StrCat(reg_prefixes[3], 5); |
| if (kR1Name[0] == 'x') { |
| this->template AppendRegisterOperands<XRegister>({kR1Name}, {}); |
| } else { |
| this->template AppendRegisterOperands<RVFpRegister>({kR1Name}, {}); |
| } |
| if (kR2Name[0] == 'x') { |
| this->template AppendRegisterOperands<XRegister>({kR2Name}, {}); |
| } else { |
| this->template AppendRegisterOperands<RVFpRegister>({kR2Name}, {}); |
| } |
| if (kR3Name[0] == 'x') { |
| this->template AppendRegisterOperands<XRegister>({kR3Name}, {}); |
| } else { |
| this->template AppendRegisterOperands<RVFpRegister>({kR3Name}, {}); |
| } |
| if (kRdName[0] == 'x') { |
| this->template AppendRegisterOperands<XRegister>({}, {kRdName}); |
| } else { |
| this->template AppendRegisterOperands<RVFpRegister>({}, {kRdName}); |
| } |
| TestRoundingModeSourceOperand *rm_source_operand = |
| new TestRoundingModeSourceOperand(); |
| this->instruction_->AppendSource(rm_source_operand); |
| auto *flag_op = |
| this->rv_fp_->fflags()->CreateSetDestinationOperand(0, "fflags"); |
| this->instruction_->AppendDestination(flag_op); |
| this->template FillArrayWithRandomFPValues<LHS>(lhs_span); |
| this->template FillArrayWithRandomFPValues<MHS>(mhs_span); |
| this->template FillArrayWithRandomFPValues<RHS>(rhs_span); |
| using LhsInt = typename FPTypeInfo<LHS>::IntType; |
| *reinterpret_cast<LhsInt *>(&lhs_span[0]) = FPTypeInfo<LHS>::kQNaN; |
| *reinterpret_cast<LhsInt *>(&lhs_span[1]) = FPTypeInfo<LHS>::kSNaN; |
| *reinterpret_cast<LhsInt *>(&lhs_span[2]) = FPTypeInfo<LHS>::kPosInf; |
| *reinterpret_cast<LhsInt *>(&lhs_span[3]) = FPTypeInfo<LHS>::kNegInf; |
| *reinterpret_cast<LhsInt *>(&lhs_span[4]) = FPTypeInfo<LHS>::kPosZero; |
| *reinterpret_cast<LhsInt *>(&lhs_span[5]) = FPTypeInfo<LHS>::kNegZero; |
| *reinterpret_cast<LhsInt *>(&lhs_span[6]) = FPTypeInfo<LHS>::kPosDenorm; |
| *reinterpret_cast<LhsInt *>(&lhs_span[7]) = FPTypeInfo<LHS>::kNegDenorm; |
| using MhsInt = typename FPTypeInfo<MHS>::IntType; |
| *reinterpret_cast<MhsInt *>(&mhs_span[8 + 0]) = FPTypeInfo<MHS>::kQNaN; |
| *reinterpret_cast<MhsInt *>(&mhs_span[8 + 1]) = FPTypeInfo<MHS>::kSNaN; |
| *reinterpret_cast<MhsInt *>(&mhs_span[8 + 2]) = FPTypeInfo<MHS>::kPosInf; |
| *reinterpret_cast<MhsInt *>(&mhs_span[8 + 3]) = FPTypeInfo<MHS>::kNegInf; |
| *reinterpret_cast<MhsInt *>(&mhs_span[8 + 4]) = FPTypeInfo<MHS>::kPosZero; |
| *reinterpret_cast<MhsInt *>(&mhs_span[8 + 5]) = FPTypeInfo<MHS>::kNegZero; |
| *reinterpret_cast<MhsInt *>(&mhs_span[8 + 6]) = FPTypeInfo<MHS>::kPosDenorm; |
| *reinterpret_cast<MhsInt *>(&mhs_span[8 + 7]) = FPTypeInfo<MHS>::kNegDenorm; |
| using RhsInt = typename FPTypeInfo<RHS>::IntType; |
| *reinterpret_cast<RhsInt *>(&rhs_span[16 + 0]) = FPTypeInfo<RHS>::kQNaN; |
| *reinterpret_cast<RhsInt *>(&rhs_span[16 + 1]) = FPTypeInfo<RHS>::kSNaN; |
| *reinterpret_cast<RhsInt *>(&rhs_span[16 + 2]) = FPTypeInfo<RHS>::kPosInf; |
| *reinterpret_cast<RhsInt *>(&rhs_span[16 + 3]) = FPTypeInfo<RHS>::kNegInf; |
| *reinterpret_cast<RhsInt *>(&rhs_span[16 + 4]) = FPTypeInfo<RHS>::kPosZero; |
| *reinterpret_cast<RhsInt *>(&rhs_span[16 + 5]) = FPTypeInfo<RHS>::kNegZero; |
| *reinterpret_cast<RhsInt *>(&rhs_span[16 + 6]) = FPTypeInfo<RHS>::kPosDenorm; |
| *reinterpret_cast<RhsInt *>(&rhs_span[16 + 7]) = FPTypeInfo<RHS>::kNegDenorm; |
| for (int i = 0; i < kTestValueLength; i++) { |
| this->template SetNaNBoxedRegisterValues<LHS, LhsRegisterType>( |
| {{kR1Name, lhs_span[i]}}); |
| this->template SetNaNBoxedRegisterValues<MHS, MhsRegisterType>( |
| {{kR2Name, mhs_span[i]}}); |
| this->template SetNaNBoxedRegisterValues<RHS, RhsRegisterType>( |
| {{kR3Name, rhs_span[i]}}); |
| |
| for (int rm : {0, 1, 2, 3, 4}) { |
| this->rv_fp_->SetRoundingMode(static_cast<FPRoundingMode>(rm)); |
| rm_source_operand->SetRoundingMode(static_cast<FPRoundingMode>(rm)); |
| this->rv_fp_->fflags()->Write(static_cast<uint32_t>(0)); |
| this->template SetRegisterValues<DestRegisterType::ValueType, |
| DestRegisterType>({{kRdName, 0}}); |
| |
| inst->Execute(nullptr); |
| // Get the fflags for the instruction execution. |
| auto instruction_fflags = this->rv_fp_->fflags()->GetUint32(); |
| this->rv_fp_->fflags()->Write(static_cast<uint32_t>(0)); |
| R op_val; |
| uint32_t test_operation_fflags; |
| { |
| ScopedFPRoundingMode scoped_rm(this->rv_fp_->host_fp_interface(), rm); |
| std::tie(op_val, test_operation_fflags) = |
| operation(lhs_span[i], mhs_span[i], rhs_span[i]); |
| } |
| auto reg_val = |
| this->state_->template GetRegister<DestRegisterType>(kRdName) |
| .first->data_buffer() |
| ->template Get<R>(0); |
| FPCompare<R>( |
| op_val, reg_val, delta_position, |
| absl::StrCat(name, " ", i, " (rm=", rm, |
| ") : ", FloatingPointToString<LHS>(lhs_span[i]), "(", |
| absl::StrFormat("%#x", lhs_span[i].value), ") ", |
| FloatingPointToString<MHS>(mhs_span[i]), "(", |
| absl::StrFormat("%#x", mhs_span[i].value), ") ", |
| FloatingPointToString<RHS>(rhs_span[i]), "(", |
| absl::StrFormat("%#x", rhs_span[i].value), ") ")); |
| |
| EXPECT_EQ(test_operation_fflags, instruction_fflags) |
| << absl::StrCat(name, " ", i, " (rm=", rm, |
| ") : ", FloatingPointToString<LHS>(lhs_span[i]), "(", |
| absl::StrFormat("%#x", lhs_span[i].value), ") ", |
| FloatingPointToString<MHS>(mhs_span[i]), "(", |
| absl::StrFormat("%#x", mhs_span[i].value), ") ", |
| FloatingPointToString<RHS>(rhs_span[i]), "(", |
| absl::StrFormat("%#x", rhs_span[i].value), ") "); |
| } |
| } |
| } |
| |
| // Verify that the fmv.h.x instruction NaN boxes the converted value. |
| template <typename XRegister> |
| template <typename FPType> |
| void RVZfhInstructionTestBase<XRegister>::FmvHxNanBoxHelper() { |
| using ScalarRegisterType = XRegister::ValueType; |
| this->template AppendRegisterOperands<RVFpRegister>({}, {"f5"}); |
| this->template AppendRegisterOperands<XRegister>({"x5"}, {}); |
| this->instruction_->AppendSource(new TestRoundingModeSourceOperand()); |
| for (int i = 0; i < 128; i++) { |
| ScalarRegisterType scalar = |
| absl::Uniform<ScalarRegisterType>(this->bitgen_); |
| this->template SetRegisterValues<ScalarRegisterType, XRegister>( |
| {{"x5", scalar}}); |
| this->template SetRegisterValues<uint64_t, RVFpRegister>({{"f5", 0}}); |
| this->instruction_->Execute(nullptr); |
| FPType observed_fp = this->state_->template GetRegister<RVFpRegister>("f5") |
| .first->data_buffer() |
| ->template Get<FPType>(0); |
| EXPECT_TRUE(std::isnan(observed_fp)); |
| } |
| } |
| |
| // Helper to test the movement of an IEEE encoded half precision value from a |
| // float register to an integer register. |
| template <typename XRegister> |
| void RVZfhInstructionTestBase<XRegister>::FmvXhHelper() { |
| using ScalarRegisterType = XRegister::ValueType; |
| this->template UnaryOpFPTestHelper<ScalarRegisterType, HalfFP>( |
| "fmv.x.h", this->instruction_, {"f", "x"}, 32, |
| [](HalfFP half_fp) -> ScalarRegisterType { |
| bool sign = 1 & (half_fp.value >> (FPTypeInfo<HalfFP>::kBitSize - 1)); |
| // Fill the upper XLEN-16 bits with the sign bit as per the spec. |
| ScalarRegisterType result = |
| sign ? (std::numeric_limits<ScalarRegisterType>::max() << 16) : 0; |
| result |= static_cast<ScalarRegisterType>(half_fp.value); |
| return result; |
| }); |
| } |
| |
| // Helper to test conversion of a 32bit integer value to a half precision float |
| // value. |
| template <typename XRegister> |
| template <typename SourceIntegerType> |
| void RVZfhInstructionTestBase<XRegister>::CvtHwHelper(absl::string_view name) { |
| UnaryOpWithFflagsMixedTestHelper<RVFpRegister, XRegister, HalfFP, |
| SourceIntegerType>( |
| name, this->instruction_, {"x", "f"}, 32, |
| [](SourceIntegerType input_int, int rm) -> std::tuple<HalfFP, uint32_t> { |
| uint32_t fflags = 0; |
| HalfFP result = FpConversionsTestHelper(static_cast<double>(input_int)) |
| .ConvertWithFlags<HalfFP>( |
| fflags, static_cast<FPRoundingMode>(rm)); |
| return std::make_tuple(result, fflags); |
| }); |
| } |
| |
| // Helper to test conversion of a half precision float value to a 32bit integer |
| // value. |
| template <typename XRegister> |
| template <typename DestinationIntegerType> |
| void RVZfhInstructionTestBase<XRegister>::CvtWhHelper(absl::string_view name) { |
| UnaryOpWithFflagsMixedTestHelper<XRegister, RVFpRegister, |
| DestinationIntegerType, HalfFP>( |
| name, this->instruction_, {"f", "x"}, 32, |
| [this](HalfFP input, |
| int rm) -> std::tuple<DestinationIntegerType, uint32_t> { |
| uint32_t fflags = 0; |
| double input_double = |
| FpConversionsTestHelper(input).ConvertWithFlags<double>(fflags); |
| const DestinationIntegerType val = |
| this->template RoundToInteger<double, DestinationIntegerType>( |
| input_double, rm, fflags); |
| return std::make_tuple(val, fflags); |
| }); |
| } |
| |
| // Helper to test the comparison of two half precision values for equality. |
| template <typename XRegister> |
| void RVZfhInstructionTestBase<XRegister>::CmpEqHelper() { |
| using ScalarRegisterType = XRegister::ValueType; |
| this->template BinaryOpWithFflagsFPTestHelper<ScalarRegisterType, HalfFP, |
| HalfFP>( |
| "feq.h", this->instruction_, {"f", "f", "x"}, 32, |
| [](HalfFP a, HalfFP b) -> std::tuple<ScalarRegisterType, uint32_t> { |
| uint32_t fflags = 0; |
| double a_f = |
| FpConversionsTestHelper(a).ConvertWithFlags<double>(fflags); |
| double b_f = |
| FpConversionsTestHelper(b).ConvertWithFlags<double>(fflags); |
| ScalarRegisterType result = a_f == b_f ? 1 : 0; |
| if (std::isnan(a_f) || std::isnan(b_f)) { |
| result = 0; |
| } |
| return std::make_tuple(result, fflags); |
| }); |
| } |
| |
| // Helper to test the comparison of two half precision values for less than. |
| template <typename XRegister> |
| void RVZfhInstructionTestBase<XRegister>::CmpLtHelper() { |
| using ScalarRegisterType = XRegister::ValueType; |
| this->template BinaryOpWithFflagsFPTestHelper<ScalarRegisterType, HalfFP, |
| HalfFP>( |
| "flt.h", this->instruction_, {"f", "f", "x"}, 32, |
| [](HalfFP a, HalfFP b) -> std::tuple<ScalarRegisterType, uint32_t> { |
| uint32_t fflags = 0; |
| double a_f = |
| FpConversionsTestHelper(a).ConvertWithFlags<double>(fflags); |
| double b_f = |
| FpConversionsTestHelper(b).ConvertWithFlags<double>(fflags); |
| ScalarRegisterType result = a_f < b_f ? 1 : 0; |
| if (std::isnan(a_f) || std::isnan(b_f)) { |
| result = 0; |
| // LT is a signaling comparison, so the invalid operation flag is |
| // set. |
| fflags |= static_cast<uint32_t>( |
| mpact::sim::riscv::FPExceptions::kInvalidOp); |
| } |
| return std::make_tuple(result, fflags); |
| }); |
| } |
| |
| // Helper to test the comparison of two half precision values for less than or |
| // equal to. |
| template <typename XRegister> |
| void RVZfhInstructionTestBase<XRegister>::CmpLeHelper() { |
| using ScalarRegisterType = XRegister::ValueType; |
| this->template BinaryOpWithFflagsFPTestHelper<ScalarRegisterType, HalfFP, |
| HalfFP>( |
| "fle.h", this->instruction_, {"f", "f", "x"}, 32, |
| [](HalfFP a, HalfFP b) -> std::tuple<ScalarRegisterType, uint32_t> { |
| uint32_t fflags = 0; |
| double a_f = |
| FpConversionsTestHelper(a).ConvertWithFlags<double>(fflags); |
| double b_f = |
| FpConversionsTestHelper(b).ConvertWithFlags<double>(fflags); |
| ScalarRegisterType result = a_f <= b_f ? 1 : 0; |
| if (std::isnan(a_f) || std::isnan(b_f)) { |
| result = 0; |
| // LE is a signaling comparison, so the invalid operation flag is |
| // set. |
| fflags |= static_cast<uint32_t>( |
| mpact::sim::riscv::FPExceptions::kInvalidOp); |
| } |
| return std::make_tuple(result, fflags); |
| }); |
| } |
| |
| // Helper to test the classification of the half precision value. |
| template <typename XRegister> |
| void RVZfhInstructionTestBase<XRegister>::ClassHelper() { |
| using ScalarRegisterType = XRegister::ValueType; |
| UnaryOpWithFflagsMixedTestHelper<XRegister, RVFpRegister, ScalarRegisterType, |
| HalfFP>( |
| "fclass.h", this->instruction_, {"f", "x"}, 32, |
| [](HalfFP input, int rm) -> std::tuple<ScalarRegisterType, uint32_t> { |
| uint16_t sign_mask = |
| ~(FPTypeInfo<HalfFP>::kExpMask | FPTypeInfo<HalfFP>::kSigMask); |
| uint16_t sign = input.value & sign_mask; |
| int shift = -1; |
| |
| switch (input.value & FPTypeInfo<HalfFP>::kExpMask) { |
| case 0: |
| if (input.value & FPTypeInfo<HalfFP>::kSigMask) { |
| shift = sign ? 2 : 5; // +/- Subnormal |
| } else { |
| shift = sign ? 3 : 4; // +/- zero |
| } |
| break; |
| case FPTypeInfo<HalfFP>::kExpMask: |
| if (input.value & FPTypeInfo<HalfFP>::kSigMask) { |
| // Quiet/Signaling NaN |
| shift = FPTypeInfo<HalfFP>::IsQNaN(input) ? 9 : 8; |
| } else { |
| shift = sign ? 0 : 7; // +/- infinity |
| } |
| break; |
| default: |
| shift = sign ? 1 : 6; // +/- normal |
| break; |
| } |
| EXPECT_GE(shift, 0) << "The test didn't set the expected result."; |
| return std::make_tuple(static_cast<ScalarRegisterType>(1) << shift, 0); |
| }); |
| } |
| |
| class RV32ZfhInstructionTest : public RVZfhInstructionTestBase<RV32Register> { |
| protected: |
| // Test conversion instructions. The instance variable semantic_function_ is |
| // used to set the semantic function for the instruction and should be set |
| // before calling this function. |
| template <typename T, typename U, int rm = 0> |
| T ConversionHelper(U input_val) { |
| // Initialize a fresh instruction. |
| ResetInstruction(); |
| assert(semantic_function_); |
| SetSemanticFunction(semantic_function_); |
| |
| // Configure source and destination operands for the instruction. |
| AppendRegisterOperands<RVFpRegister>({"f1"}, {"f5"}); |
| instruction_->AppendSource(new TestRoundingModeSourceOperand()); |
| auto *flag_op = rv_fp_->fflags()->CreateSetDestinationOperand(0, "fflags"); |
| instruction_->AppendDestination(flag_op); |
| assert(instruction_->SourcesSize() == 2); |
| assert(instruction_->DestinationsSize() == 2); |
| |
| // Set all operands to known values before executing the instruction. |
| static_cast<TestRoundingModeSourceOperand *>(instruction_->Source(1)) |
| ->SetRoundingMode(static_cast<FPRoundingMode>(rm)); |
| rv_fp_->SetRoundingMode(static_cast<FPRoundingMode>(rm)); |
| SetNaNBoxedRegisterValues<U, RVFpRegister>({{"f1", input_val}}); |
| SetRegisterValues<int, RVFpRegister>({{"f5", 0xDEAFBEEFDEADBEEF}}); |
| rv_fp_->fflags()->Write(static_cast<uint32_t>(0)); |
| |
| instruction_->Execute(nullptr); |
| T reg_val = state_->GetRegister<RVFpRegister>("f5") |
| .first->data_buffer() |
| ->template Get<T>(0); |
| return reg_val; |
| } |
| |
| template <FPRoundingMode> |
| void RoundingConversionTestHelper(uint32_t, uint16_t, uint32_t &, uint32_t, |
| uint16_t, uint32_t &); |
| |
| template <FPRoundingMode rm> |
| void RoundingPointTest(uint16_t); |
| |
| Instruction::SemanticFunction semantic_function_ = nullptr; |
| }; |
| |
| template <FPRoundingMode rm> |
| void RV32ZfhInstructionTest::RoundingConversionTestHelper( |
| uint32_t float_uint_before, uint16_t half_uint_before, |
| uint32_t &first_expected_fflags, uint32_t float_uint_after, |
| uint16_t half_uint_after, uint32_t &second_expected_fflags) { |
| float input_val; |
| HalfFP expected_val; |
| HalfFP actual_val; |
| |
| input_val = absl::bit_cast<float>(float_uint_before); |
| expected_val = {.value = half_uint_before}; |
| actual_val = ConversionHelper<HalfFP, float, static_cast<int>(rm)>(input_val); |
| EXPECT_EQ(expected_val.value, actual_val.value) |
| << "expected: " << std::hex << expected_val.value |
| << ", actual: " << std::hex << actual_val.value |
| << ", float_uint: " << std::hex << float_uint_before |
| << ", rounding_mode: " << static_cast<int>(rm); |
| EXPECT_EQ(first_expected_fflags, rv_fp_->fflags()->GetUint32()) |
| << "while converting: " << std::hex << float_uint_before |
| << " to:" << std::hex << actual_val.value |
| << " with rounding mode: " << static_cast<int>(rm); |
| |
| input_val = absl::bit_cast<float>(float_uint_after); |
| expected_val = {.value = half_uint_after}; |
| actual_val = ConversionHelper<HalfFP, float, static_cast<int>(rm)>(input_val); |
| EXPECT_EQ(expected_val.value, actual_val.value) |
| << "expected: " << std::hex << expected_val.value |
| << ", actual: " << std::hex << actual_val.value |
| << ", float_uint: " << std::hex << float_uint_after |
| << ", rounding_mode: " << static_cast<int>(rm); |
| EXPECT_EQ(second_expected_fflags, rv_fp_->fflags()->GetUint32()) |
| << "while converting: " << std::hex << float_uint_after |
| << " to:" << std::hex << actual_val.value |
| << " with rounding mode: " << static_cast<int>(rm); |
| } |
| |
| // Test the FP16 load instruction. The semantic functions should match the isa |
| // file. |
| TEST_F(RV32ZfhInstructionTest, RiscVFlh) { |
| SetSemanticFunction(&::mpact::sim::riscv::RV32::RiscVILhu); |
| SetChildInstruction(); |
| SetChildSemanticFunction(&RiscVZfhFlhChild); |
| |
| SetupMemory<uint32_t, uint16_t>(0xFF, 0xBEEF); |
| |
| HalfFP observed_val = |
| LoadHalfHelper<HalfFP>(/* 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(RV32ZfhInstructionTest, RiscVFlh_float_nanbox) { |
| SetSemanticFunction(&::mpact::sim::riscv::RV32::RiscVILhu); |
| SetChildInstruction(); |
| SetChildSemanticFunction(&RiscVZfhFlhChild); |
| |
| SetupMemory<uint32_t, uint16_t>(0xFF, 0xBEEF); |
| |
| float observed_val = LoadHalfHelper<float>(/* 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(RV32ZfhInstructionTest, RiscVFlh_double_nanbox) { |
| SetSemanticFunction(&::mpact::sim::riscv::RV32::RiscVILhu); |
| SetChildInstruction(); |
| SetChildSemanticFunction(&RiscVZfhFlhChild); |
| |
| SetupMemory<uint32_t, uint16_t>(0xFF, 0xBEEF); |
| |
| double observed_val = LoadHalfHelper<double>( |
| /* base */ 0x0100, /* offset */ -1); |
| EXPECT_TRUE(std::isnan(observed_val)); |
| } |
| |
| // Move half precision from a float register to an integer register. The IEEE754 |
| // encoding is preserved in the integer register. |
| TEST_F(RV32ZfhInstructionTest, RiscVZfhFMvxh) { |
| SetSemanticFunction(&::mpact::sim::riscv::RV32::RiscVZfhFMvxh); |
| FmvXhHelper(); |
| } |
| |
| // Move half precision from an integer register (lower 16 bits) to a float |
| // register. |
| TEST_F(RV32ZfhInstructionTest, RiscVZfhFMvhx) { |
| SetSemanticFunction(&::mpact::sim::riscv::RV32::RiscVZfhFMvhx); |
| UnaryOpFPTestHelper<HalfFP, uint32_t>( |
| "fmv.h.x", instruction_, {"x", "f"}, 32, [](uint32_t scalar) -> HalfFP { |
| return HalfFP{.value = static_cast<uint16_t>(scalar)}; |
| }); |
| } |
| |
| // Move half precision from an integer register to a float register. Confirm |
| // that the destination value is NaN boxed. |
| TEST_F(RV32ZfhInstructionTest, RiscVZfhFMvhx_float_nanbox) { |
| SetSemanticFunction(&::mpact::sim::riscv::RV32::RiscVZfhFMvhx); |
| FmvHxNanBoxHelper<float>(); |
| } |
| |
| // Move half precision from an integer register to a float register. Confirm |
| // that the destination value is NaN boxed. |
| TEST_F(RV32ZfhInstructionTest, RiscVZfhFMvhx_double_nanbox) { |
| SetSemanticFunction(&::mpact::sim::riscv::RV32::RiscVZfhFMvhx); |
| FmvHxNanBoxHelper<double>(); |
| } |
| |
| // Half precision to single precision conversion. |
| TEST_F(RV32ZfhInstructionTest, RiscVZfhCvtSh) { |
| SetSemanticFunction(&RiscVZfhCvtSh); |
| UnaryOpWithFflagsFPTestHelper<float, HalfFP>( |
| "fcvt.s.h", instruction_, {"f", "f"}, 32, |
| [](HalfFP half_fp, int rm) -> std::tuple<float, uint32_t> { |
| uint32_t fflags = 0; |
| float float_result = |
| FpConversionsTestHelper(half_fp).ConvertWithFlags<float>( |
| fflags, static_cast<FPRoundingMode>(rm)); |
| return std::make_tuple(float_result, fflags); |
| }); |
| } |
| |
| // Single precision to half precision conversion. |
| TEST_F(RV32ZfhInstructionTest, RiscVZfhCvtHs) { |
| SetSemanticFunction(&RiscVZfhCvtHs); |
| UnaryOpWithFflagsFPTestHelper<HalfFP, float>( |
| "fcvt.h.s", instruction_, {"f", "f"}, 32, |
| [](float input_float, int rm) -> std::tuple<HalfFP, uint32_t> { |
| uint32_t fflags = 0; |
| HalfFP half_result = FpConversionsTestHelper(input_float) |
| .ConvertWithFlags<HalfFP>( |
| fflags, static_cast<FPRoundingMode>(rm)); |
| return std::make_tuple(half_result, fflags); |
| }); |
| } |
| |
| // Find the nearest floats that convert to the given half precision values. |
| std::tuple<uint32_t, uint32_t> GetRoundingPoints(uint16_t first, |
| uint16_t second, |
| FPRoundingMode rm) { |
| uint16_t upper_uhalf = std::max(first, second); |
| uint32_t upper_ufloat = absl::bit_cast<uint32_t>( |
| FpConversionsTestHelper(upper_uhalf).Convert<float>(rm)); |
| |
| uint16_t lower_uhalf = std::min(first, second); |
| uint32_t lower_ufloat = absl::bit_cast<uint32_t>( |
| FpConversionsTestHelper(lower_uhalf).Convert<float>(rm)); |
| while (upper_ufloat - lower_ufloat > 1) { |
| uint32_t udelta = upper_ufloat - lower_ufloat; |
| uint32_t mid_ufloat = lower_ufloat + (udelta >> 1); |
| HalfFP mid_half = FpConversionsTestHelper(mid_ufloat).Convert<HalfFP>(rm); |
| uint16_t mid_uhalf = mid_half.value; |
| if (upper_uhalf == mid_uhalf) { |
| upper_ufloat = mid_ufloat; |
| } else if (lower_uhalf == mid_uhalf) { |
| lower_ufloat = mid_ufloat; |
| } |
| } |
| if (first > second) { |
| return std::make_tuple(upper_ufloat, lower_ufloat); |
| } |
| return std::make_tuple(lower_ufloat, upper_ufloat); |
| } |
| |
| template <FPRoundingMode rm> |
| void RV32ZfhInstructionTest::RoundingPointTest(uint16_t base_uhalf) { |
| uint16_t first_uhalf = base_uhalf, second_uhalf = base_uhalf + 1; |
| uint32_t first_ufloat, second_ufloat; |
| uint32_t first_expected_fflags = 0, second_expected_fflags = 0; |
| |
| std::tie(first_ufloat, second_ufloat) = |
| GetRoundingPoints(first_uhalf, second_uhalf, rm); |
| // Get the expected fflags |
| FpConversionsTestHelper(first_ufloat) |
| .ConvertWithFlags<HalfFP>(first_expected_fflags, rm); |
| FpConversionsTestHelper(second_ufloat) |
| .ConvertWithFlags<HalfFP>(second_expected_fflags, rm); |
| RoundingConversionTestHelper<rm>(first_ufloat, first_uhalf, |
| first_expected_fflags, second_ufloat, |
| second_uhalf, second_expected_fflags); |
| } |
| |
| // Verify that the rounding points in the semantic functions match the |
| // rounding points in the test helpers. Test across all rounding modes. |
| TEST_F(RV32ZfhInstructionTest, |
| RiscVZfhCvtHs_conversion_rounding_points_first_nonzero) { |
| semantic_function_ = &RiscVZfhCvtHs; |
| // The first float that converts to a non zero half precision value after |
| // rounding. Zero before, denormal after. |
| uint16_t pos_uhalf = 0b0'00000'0000000000; |
| RoundingPointTest<FPRoundingMode::kRoundToNearest>(pos_uhalf); |
| RoundingPointTest<FPRoundingMode::kRoundTowardsZero>(pos_uhalf); |
| RoundingPointTest<FPRoundingMode::kRoundDown>(pos_uhalf); |
| RoundingPointTest<FPRoundingMode::kRoundUp>(pos_uhalf); |
| |
| uint16_t neg_uhalf = 0b1'00000'0000000000; |
| RoundingPointTest<FPRoundingMode::kRoundToNearest>(neg_uhalf); |
| RoundingPointTest<FPRoundingMode::kRoundTowardsZero>(neg_uhalf); |
| RoundingPointTest<FPRoundingMode::kRoundDown>(neg_uhalf); |
| RoundingPointTest<FPRoundingMode::kRoundUp>(neg_uhalf); |
| } |
| |
| TEST_F(RV32ZfhInstructionTest, |
| RiscVZfhCvtHs_conversion_rounding_points_denrom_denorm) { |
| semantic_function_ = &RiscVZfhCvtHs; |
| |
| // Rounding denormal before and denormal after |
| uint16_t pos_uhalf = 0b0'00000'0000000001; |
| RoundingPointTest<FPRoundingMode::kRoundToNearest>(pos_uhalf); |
| RoundingPointTest<FPRoundingMode::kRoundTowardsZero>(pos_uhalf); |
| RoundingPointTest<FPRoundingMode::kRoundDown>(pos_uhalf); |
| RoundingPointTest<FPRoundingMode::kRoundUp>(pos_uhalf); |
| |
| uint16_t neg_uhalf = 0b1'00000'0000000001; |
| RoundingPointTest<FPRoundingMode::kRoundToNearest>(neg_uhalf); |
| RoundingPointTest<FPRoundingMode::kRoundTowardsZero>(neg_uhalf); |
| RoundingPointTest<FPRoundingMode::kRoundDown>(neg_uhalf); |
| RoundingPointTest<FPRoundingMode::kRoundUp>(neg_uhalf); |
| } |
| |
| TEST_F(RV32ZfhInstructionTest, |
| RiscVZfhCvtHs_conversion_rounding_points_denorm_normal) { |
| semantic_function_ = &RiscVZfhCvtHs; |
| |
| // The rounding overflows the significand and should increase the exponent. |
| // Denormal before, normal after. |
| uint16_t pos_uhalf = 0b0'00000'1111111111; |
| RoundingPointTest<FPRoundingMode::kRoundToNearest>(pos_uhalf); |
| RoundingPointTest<FPRoundingMode::kRoundTowardsZero>(pos_uhalf); |
| RoundingPointTest<FPRoundingMode::kRoundDown>(pos_uhalf); |
| RoundingPointTest<FPRoundingMode::kRoundUp>(pos_uhalf); |
| |
| uint16_t neg_uhalf = 0b1'00000'1111111111; |
| RoundingPointTest<FPRoundingMode::kRoundToNearest>(neg_uhalf); |
| RoundingPointTest<FPRoundingMode::kRoundTowardsZero>(neg_uhalf); |
| RoundingPointTest<FPRoundingMode::kRoundDown>(neg_uhalf); |
| RoundingPointTest<FPRoundingMode::kRoundUp>(neg_uhalf); |
| } |
| |
| TEST_F(RV32ZfhInstructionTest, |
| RiscVZfhCvtHs_conversion_rounding_points_normal_normal) { |
| semantic_function_ = &RiscVZfhCvtHs; |
| |
| // Rounding normal before and normal after. |
| uint16_t pos_uhalf = 0b0'11110'1111111110; |
| RoundingPointTest<FPRoundingMode::kRoundToNearest>(pos_uhalf); |
| RoundingPointTest<FPRoundingMode::kRoundTowardsZero>(pos_uhalf); |
| RoundingPointTest<FPRoundingMode::kRoundDown>(pos_uhalf); |
| RoundingPointTest<FPRoundingMode::kRoundUp>(pos_uhalf); |
| |
| uint16_t neg_uhalf = 0b1'11110'1111111110; |
| RoundingPointTest<FPRoundingMode::kRoundToNearest>(neg_uhalf); |
| RoundingPointTest<FPRoundingMode::kRoundTowardsZero>(neg_uhalf); |
| RoundingPointTest<FPRoundingMode::kRoundDown>(neg_uhalf); |
| RoundingPointTest<FPRoundingMode::kRoundUp>(neg_uhalf); |
| } |
| |
| TEST_F(RV32ZfhInstructionTest, RiscVZfhCvtHs_conversion_rounding_points_inf) { |
| semantic_function_ = &RiscVZfhCvtHs; |
| |
| // Rounding normal before and infinity after. |
| uint16_t pos_uhalf = 0b0'11110'1111111111; |
| RoundingPointTest<FPRoundingMode::kRoundToNearest>(pos_uhalf); |
| RoundingPointTest<FPRoundingMode::kRoundTowardsZero>(pos_uhalf); |
| RoundingPointTest<FPRoundingMode::kRoundDown>(pos_uhalf); |
| RoundingPointTest<FPRoundingMode::kRoundUp>(pos_uhalf); |
| |
| uint16_t neg_uhalf = 0b1'11110'1111111111; |
| RoundingPointTest<FPRoundingMode::kRoundToNearest>(neg_uhalf); |
| RoundingPointTest<FPRoundingMode::kRoundTowardsZero>(neg_uhalf); |
| RoundingPointTest<FPRoundingMode::kRoundDown>(neg_uhalf); |
| RoundingPointTest<FPRoundingMode::kRoundUp>(neg_uhalf); |
| } |
| |
| // Half precision to double precision conversion. |
| TEST_F(RV32ZfhInstructionTest, RiscVZfhCvtDh) { |
| SetSemanticFunction(&RiscVZfhCvtDh); |
| UnaryOpWithFflagsFPTestHelper<double, HalfFP>( |
| "fcvt.d.h", instruction_, {"f", "f"}, 32, |
| [](HalfFP half_fp, int rm) -> std::tuple<double, uint32_t> { |
| uint32_t fflags = 0; |
| double double_result = |
| FpConversionsTestHelper(half_fp).ConvertWithFlags<double>( |
| fflags, static_cast<FPRoundingMode>(rm)); |
| return std::make_tuple(double_result, fflags); |
| }); |
| } |
| |
| // Double precision to half precision conversion. |
| TEST_F(RV32ZfhInstructionTest, RiscVZfhCvtHd) { |
| SetSemanticFunction(&RiscVZfhCvtHd); |
| UnaryOpWithFflagsFPTestHelper<HalfFP, double>( |
| "fcvt.h.d", instruction_, {"f", "f"}, 32, |
| [](double input_double, int rm) -> std::tuple<HalfFP, uint32_t> { |
| uint32_t fflags = 0; |
| HalfFP half_result = FpConversionsTestHelper(input_double) |
| .ConvertWithFlags<HalfFP>( |
| fflags, static_cast<FPRoundingMode>(rm)); |
| return std::make_tuple(half_result, fflags); |
| }); |
| } |
| |
| // A test to make sure +0 and -0 are converted correctly. Mutation testing |
| // inspired this test. |
| TEST_F(RV32ZfhInstructionTest, RiscVZfhCvtSh_strict_zeros) { |
| semantic_function_ = &RiscVZfhCvtSh; |
| uint32_t expected_p0 = FPTypeInfo<float>::kPosZero; |
| uint32_t expected_n0 = FPTypeInfo<float>::kNegZero; |
| float actual_p0; |
| float actual_n0; |
| HalfFP pos_zero = {.value = FPTypeInfo<HalfFP>::kPosZero}; |
| HalfFP neg_zero = {.value = FPTypeInfo<HalfFP>::kNegZero}; |
| |
| actual_p0 = |
| ConversionHelper<float, HalfFP, kRoundingModeRoundToNearest>(pos_zero); |
| actual_n0 = |
| ConversionHelper<float, HalfFP, kRoundingModeRoundToNearest>(neg_zero); |
| EXPECT_EQ(absl::bit_cast<uint32_t>(actual_p0), expected_p0); |
| EXPECT_EQ(absl::bit_cast<uint32_t>(actual_n0), expected_n0); |
| |
| actual_p0 = |
| ConversionHelper<float, HalfFP, kRoundingModeRoundTowardsZero>(pos_zero); |
| actual_n0 = |
| ConversionHelper<float, HalfFP, kRoundingModeRoundTowardsZero>(neg_zero); |
| EXPECT_EQ(absl::bit_cast<uint32_t>(actual_p0), expected_p0); |
| EXPECT_EQ(absl::bit_cast<uint32_t>(actual_n0), expected_n0); |
| |
| actual_p0 = ConversionHelper<float, HalfFP, kRoundingModeRoundDown>(pos_zero); |
| actual_n0 = ConversionHelper<float, HalfFP, kRoundingModeRoundDown>(neg_zero); |
| EXPECT_EQ(absl::bit_cast<uint32_t>(actual_p0), expected_p0); |
| EXPECT_EQ(absl::bit_cast<uint32_t>(actual_n0), expected_n0); |
| |
| actual_p0 = ConversionHelper<float, HalfFP, kRoundingModeRoundUp>(pos_zero); |
| actual_n0 = ConversionHelper<float, HalfFP, kRoundingModeRoundUp>(neg_zero); |
| EXPECT_EQ(absl::bit_cast<uint32_t>(actual_p0), expected_p0); |
| EXPECT_EQ(absl::bit_cast<uint32_t>(actual_n0), expected_n0); |
| } |
| |
| // A test to make sure +0 and -0 are converted correctly. Mutation testing |
| // inspired this test. |
| TEST_F(RV32ZfhInstructionTest, RiscVZfhCvtHs_strict_zeros) { |
| semantic_function_ = &RiscVZfhCvtHs; |
| uint16_t expected_p0 = FPTypeInfo<HalfFP>::kPosZero; |
| uint16_t expected_n0 = FPTypeInfo<HalfFP>::kNegZero; |
| HalfFP actual_p0; |
| HalfFP actual_n0; |
| float pos_zero = absl::bit_cast<float>(FPTypeInfo<float>::kPosZero); |
| float neg_zero = absl::bit_cast<float>(FPTypeInfo<float>::kNegZero); |
| actual_p0 = |
| ConversionHelper<HalfFP, float, kRoundingModeRoundToNearest>(pos_zero); |
| actual_n0 = |
| ConversionHelper<HalfFP, float, kRoundingModeRoundToNearest>(neg_zero); |
| EXPECT_EQ(absl::bit_cast<uint16_t>(actual_p0), expected_p0); |
| EXPECT_EQ(absl::bit_cast<uint16_t>(actual_n0), expected_n0); |
| |
| actual_p0 = |
| ConversionHelper<HalfFP, float, kRoundingModeRoundTowardsZero>(pos_zero); |
| actual_n0 = |
| ConversionHelper<HalfFP, float, kRoundingModeRoundTowardsZero>(neg_zero); |
| EXPECT_EQ(absl::bit_cast<uint16_t>(actual_p0), expected_p0); |
| EXPECT_EQ(absl::bit_cast<uint16_t>(actual_n0), expected_n0); |
| |
| actual_p0 = ConversionHelper<HalfFP, float, kRoundingModeRoundDown>(pos_zero); |
| actual_n0 = ConversionHelper<HalfFP, float, kRoundingModeRoundDown>(neg_zero); |
| EXPECT_EQ(absl::bit_cast<uint16_t>(actual_p0), expected_p0); |
| EXPECT_EQ(absl::bit_cast<uint16_t>(actual_n0), expected_n0); |
| |
| actual_p0 = ConversionHelper<HalfFP, float, kRoundingModeRoundUp>(pos_zero); |
| actual_n0 = ConversionHelper<HalfFP, float, kRoundingModeRoundUp>(neg_zero); |
| EXPECT_EQ(absl::bit_cast<uint16_t>(actual_p0), expected_p0); |
| EXPECT_EQ(absl::bit_cast<uint16_t>(actual_n0), expected_n0); |
| } |
| |
| // Add half precision values. Generate the expected result by using a natively |
| // supported float datatype for the operation. |
| TEST_F(RV32ZfhInstructionTest, RiscVZfhFadd) { |
| SetSemanticFunction(&RiscVZfhFadd); |
| BinaryOpWithFflagsFPTestHelper<HalfFP, HalfFP, HalfFP>( |
| "fadd.h", instruction_, {"f", "f", "f"}, 32, |
| [this](HalfFP a, HalfFP b) -> std::tuple<HalfFP, uint32_t> { |
| FPRoundingMode rm = rv_fp_->GetRoundingMode(); |
| uint32_t fflags = 0; |
| double a_f = |
| FpConversionsTestHelper(a).ConvertWithFlags<double>(fflags); |
| double b_f = |
| FpConversionsTestHelper(b).ConvertWithFlags<double>(fflags); |
| HalfFP result = |
| FpConversionsTestHelper(a_f + b_f).ConvertWithFlags<HalfFP>(fflags, |
| rm); |
| // inf + -inf, -inf + inf are both invalid. |
| if (std::isinf(a_f) && std::isinf(b_f) && a.value != b.value) { |
| fflags |= static_cast<uint32_t>( |
| mpact::sim::riscv::FPExceptions::kInvalidOp); |
| result.value = FPTypeInfo<HalfFP>::kCanonicalNaN; |
| } |
| return std::make_tuple(result, fflags); |
| }); |
| } |
| |
| // Subtract half precision values. Generate the expected result by using a |
| // natively supported float datatype for the operation. |
| TEST_F(RV32ZfhInstructionTest, RiscVZfhFsub) { |
| SetSemanticFunction(&RiscVZfhFsub); |
| BinaryOpWithFflagsFPTestHelper<HalfFP, HalfFP, HalfFP>( |
| "fsub.h", instruction_, {"f", "f", "f"}, 32, |
| [this](HalfFP a, HalfFP b) -> std::tuple<HalfFP, uint32_t> { |
| FPRoundingMode rm = rv_fp_->GetRoundingMode(); |
| uint32_t fflags = 0; |
| double a_f = |
| FpConversionsTestHelper(a).ConvertWithFlags<double>(fflags); |
| double b_f = |
| FpConversionsTestHelper(b).ConvertWithFlags<double>(fflags); |
| HalfFP result = |
| FpConversionsTestHelper(a_f - b_f).ConvertWithFlags<HalfFP>(fflags, |
| rm); |
| // inf - inf and -inf - -inf are both invalid. |
| if (std::isinf(a_f) && std::isinf(b_f) && a.value == b.value) { |
| fflags |= static_cast<uint32_t>( |
| mpact::sim::riscv::FPExceptions::kInvalidOp); |
| result.value = FPTypeInfo<HalfFP>::kCanonicalNaN; |
| } |
| return std::make_tuple(result, fflags); |
| }); |
| } |
| |
| // Multiply half precision values. Generate the expected result by using a |
| // natively supported float datatype for the operation. |
| TEST_F(RV32ZfhInstructionTest, RiscVZfhFmul) { |
| SetSemanticFunction(&RiscVZfhFmul); |
| BinaryOpWithFflagsFPTestHelper<HalfFP, HalfFP, HalfFP>( |
| "fmul.h", instruction_, {"f", "f", "f"}, 32, |
| [this](HalfFP a, HalfFP b) -> std::tuple<HalfFP, uint32_t> { |
| FPRoundingMode rm = rv_fp_->GetRoundingMode(); |
| uint32_t fflags = 0; |
| double a_f = |
| FpConversionsTestHelper(a).ConvertWithFlags<double>(fflags); |
| double b_f = |
| FpConversionsTestHelper(b).ConvertWithFlags<double>(fflags); |
| HalfFP result = |
| FpConversionsTestHelper(a_f * b_f).ConvertWithFlags<HalfFP>(fflags, |
| rm); |
| // Multiplying infinity and zero is invalid. |
| if ((std::isinf(a_f) && b_f == 0) || (a_f == 0 && std::isinf(b_f))) { |
| fflags |= static_cast<uint32_t>( |
| mpact::sim::riscv::FPExceptions::kInvalidOp); |
| result.value = FPTypeInfo<HalfFP>::kCanonicalNaN; |
| } |
| return std::make_tuple(result, fflags); |
| }); |
| } |
| |
| // Divide half precision values. Generate the expected result by using a |
| // natively supported float datatype for the operation. |
| TEST_F(RV32ZfhInstructionTest, RiscVZfhFdiv) { |
| SetSemanticFunction(&RiscVZfhFdiv); |
| BinaryOpWithFflagsFPTestHelper<HalfFP, HalfFP, HalfFP>( |
| "fdiv.h", instruction_, {"f", "f", "f"}, 32, |
| [this](HalfFP a, HalfFP b) -> std::tuple<HalfFP, uint32_t> { |
| FPRoundingMode rm = rv_fp_->GetRoundingMode(); |
| uint32_t fflags = 0; |
| double a_f = |
| FpConversionsTestHelper(a).ConvertWithFlags<double>(fflags); |
| double b_f = |
| FpConversionsTestHelper(b).ConvertWithFlags<double>(fflags); |
| HalfFP result = |
| FpConversionsTestHelper(a_f / b_f).ConvertWithFlags<HalfFP>(fflags, |
| rm); |
| if ((a_f == 0 && b_f == 0) || (std::isinf(a_f) && std::isinf(b_f))) { |
| // 0 / 0 and inf / inf are both invalid. |
| fflags |= static_cast<uint32_t>( |
| mpact::sim::riscv::FPExceptions::kInvalidOp); |
| result.value = FPTypeInfo<HalfFP>::kCanonicalNaN; |
| } else if (!FPTypeInfo<HalfFP>::IsNaN(a) && |
| !FPTypeInfo<HalfFP>::IsInf(a) && |
| (b.value == FPTypeInfo<HalfFP>::kPosZero || |
| b.value == FPTypeInfo<HalfFP>::kNegZero)) { |
| // Dividing by zero requires an exception for non-NaN dividend values. |
| result.value = ((a.value ^ b.value) & FPTypeInfo<HalfFP>::kNegZero) | |
| (FPTypeInfo<HalfFP>::kPosInf); |
| fflags |= static_cast<uint32_t>( |
| mpact::sim::riscv::FPExceptions::kDivByZero); |
| } |
| return std::make_tuple(result, fflags); |
| }); |
| } |
| |
| // Find the minimum of two half precision values. Generate the expected result |
| // by using a natively supported float datatype for the operation. |
| TEST_F(RV32ZfhInstructionTest, RiscVZfhFmin) { |
| SetSemanticFunction(&RiscVZfhFmin); |
| BinaryOpWithFflagsFPTestHelper<HalfFP, HalfFP, HalfFP>( |
| "fmin.h", instruction_, {"f", "f", "f"}, 32, |
| [this](HalfFP a, HalfFP b) -> std::tuple<HalfFP, uint32_t> { |
| FPRoundingMode rm = rv_fp_->GetRoundingMode(); |
| uint32_t fflags = 0; |
| double a_f = |
| FpConversionsTestHelper(a).ConvertWithFlags<double>(fflags); |
| double b_f = |
| FpConversionsTestHelper(b).ConvertWithFlags<double>(fflags); |
| double min_f = 0; |
| if (a_f == 0 && b_f == 0 && (a.value != b.value)) { |
| // Special case for -0 vs +0. |
| min_f = absl::bit_cast<double>(FPTypeInfo<double>::kNegZero); |
| } else if (std::isnan(a_f) && !std::isnan(b_f)) { |
| min_f = b_f; // pick the non-NaN value |
| } else if (!std::isnan(a_f) && std::isnan(b_f)) { |
| min_f = a_f; // pick the non-NaN value |
| } else if (std::isnan(a_f) && std::isnan(b_f)) { |
| min_f = absl::bit_cast<double>(FPTypeInfo<double>::kCanonicalNaN); |
| } else if (std::isinf(a_f) && !std::isinf(b_f)) { |
| min_f = a_f < 0 ? a_f : b_f; // min(+/-inf, x) |
| } else if (!std::isinf(a_f) && std::isinf(b_f)) { |
| min_f = b_f < 0 ? b_f : a_f; // min(x, +/-inf) |
| } else { |
| if (a_f < b_f) { |
| min_f = a_f; |
| } else { |
| min_f = b_f; |
| } |
| } |
| HalfFP result = |
| FpConversionsTestHelper(min_f).ConvertWithFlags<HalfFP>(fflags, rm); |
| return std::make_tuple(result, fflags); |
| }); |
| } |
| |
| // Find the maximum of two half precision values. Generate the expected result |
| // by using a natively supported float datatype for the operation. |
| TEST_F(RV32ZfhInstructionTest, RiscVZfhFmax) { |
| SetSemanticFunction(&RiscVZfhFmax); |
| BinaryOpWithFflagsFPTestHelper<HalfFP, HalfFP, HalfFP>( |
| "fmax.h", instruction_, {"f", "f", "f"}, 32, |
| [this](HalfFP a, HalfFP b) -> std::tuple<HalfFP, uint32_t> { |
| FPRoundingMode rm = rv_fp_->GetRoundingMode(); |
| uint32_t fflags = 0; |
| double a_f = |
| FpConversionsTestHelper(a).ConvertWithFlags<double>(fflags); |
| double b_f = |
| FpConversionsTestHelper(b).ConvertWithFlags<double>(fflags); |
| double max_f = 0; |
| if (a_f == 0 && b_f == 0 && (a.value != b.value)) { |
| // Special case for -0 vs +0. |
| max_f = absl::bit_cast<double>(FPTypeInfo<double>::kPosZero); |
| } else if (std::isnan(a_f) && !std::isnan(b_f)) { |
| max_f = b_f; // pick the non-NaN value |
| } else if (!std::isnan(a_f) && std::isnan(b_f)) { |
| max_f = a_f; // pick the non-NaN value |
| } else if (std::isnan(a_f) && std::isnan(b_f)) { |
| max_f = absl::bit_cast<double>(FPTypeInfo<double>::kCanonicalNaN); |
| } else if (std::isinf(a_f) && !std::isinf(b_f)) { |
| max_f = a_f > 0 ? a_f : b_f; // max(+/-inf, x) |
| } else if (!std::isinf(a_f) && std::isinf(b_f)) { |
| max_f = b_f > 0 ? b_f : a_f; // max(x, +/-inf) |
| } else { |
| if (a_f > b_f) { |
| max_f = a_f; |
| } else { |
| max_f = b_f; |
| } |
| } |
| HalfFP result = |
| FpConversionsTestHelper(max_f).ConvertWithFlags<HalfFP>(fflags, rm); |
| return std::make_tuple(result, fflags); |
| }); |
| } |
| |
| // Test sign injection for half precision values. |
| TEST_F(RV32ZfhInstructionTest, RiscVZfhFsgnj) { |
| SetSemanticFunction(&RiscVZfhFsgnj); |
| BinaryOpWithFflagsFPTestHelper<HalfFP, HalfFP, HalfFP>( |
| "fsgnj.h", instruction_, {"f", "f", "f"}, 32, |
| [](HalfFP a, HalfFP b) -> std::tuple<HalfFP, uint32_t> { |
| HalfFP result{.value = 0}; |
| result.value |= a.value & (FPTypeInfo<HalfFP>::kExpMask | |
| FPTypeInfo<HalfFP>::kSigMask); |
| result.value |= b.value & ~(FPTypeInfo<HalfFP>::kExpMask | |
| FPTypeInfo<HalfFP>::kSigMask); |
| return std::make_tuple(result, 0); |
| }); |
| } |
| |
| // Test sign injection for half precision values with the opposite sign bit. |
| TEST_F(RV32ZfhInstructionTest, RiscVZfhFsgnjn) { |
| SetSemanticFunction(&RiscVZfhFsgnjn); |
| BinaryOpWithFflagsFPTestHelper<HalfFP, HalfFP, HalfFP>( |
| "fsgnjn.h", instruction_, {"f", "f", "f"}, 32, |
| [](HalfFP a, HalfFP b) -> std::tuple<HalfFP, uint32_t> { |
| HalfFP result{.value = 0}; |
| result.value |= a.value & (FPTypeInfo<HalfFP>::kExpMask | |
| FPTypeInfo<HalfFP>::kSigMask); |
| result.value |= (~b.value) & ~(FPTypeInfo<HalfFP>::kExpMask | |
| FPTypeInfo<HalfFP>::kSigMask); |
| return std::make_tuple(result, 0); |
| }); |
| } |
| |
| // Test sign injection for half precision values with the xor sign bit.. |
| TEST_F(RV32ZfhInstructionTest, RiscVZfhFsgnjx) { |
| SetSemanticFunction(&RiscVZfhFsgnjx); |
| BinaryOpWithFflagsFPTestHelper<HalfFP, HalfFP, HalfFP>( |
| "fsgnjn.h", instruction_, {"f", "f", "f"}, 32, |
| [](HalfFP a, HalfFP b) -> std::tuple<HalfFP, uint32_t> { |
| HalfFP result{.value = 0}; |
| result.value |= a.value & (FPTypeInfo<HalfFP>::kExpMask | |
| FPTypeInfo<HalfFP>::kSigMask); |
| result.value |= (a.value ^ b.value) & ~(FPTypeInfo<HalfFP>::kExpMask | |
| FPTypeInfo<HalfFP>::kSigMask); |
| return std::make_tuple(result, 0); |
| }); |
| } |
| |
| // Test square root for half precision values. |
| TEST_F(RV32ZfhInstructionTest, RiscVZfhFsqrt) { |
| SetSemanticFunction(&RiscVZfhFsqrt); |
| UnaryOpWithFflagsFPTestHelper<HalfFP, HalfFP>( |
| "fsqrt.h", instruction_, {"f", "f"}, 32, |
| [](HalfFP input_half, int rm) -> std::tuple<HalfFP, uint32_t> { |
| uint32_t fflags = 0; |
| double input_double_f = FpConversionsTestHelper(input_half) |
| .ConvertWithFlags<double>(fflags); |
| HalfFP result; |
| if (FPTypeInfo<HalfFP>::IsNaN(input_half)) { |
| result.value = FPTypeInfo<HalfFP>::kCanonicalNaN; |
| if (!FPTypeInfo<HalfFP>::IsQNaN(input_half)) { |
| fflags |= static_cast<uint32_t>( |
| mpact::sim::riscv::FPExceptions::kInvalidOp); |
| } |
| } else if (std::isinf(input_double_f) && input_double_f > 0) { |
| result.value = FPTypeInfo<HalfFP>::kPosInf; |
| } else if (input_double_f < 0) { |
| result.value = FPTypeInfo<HalfFP>::kCanonicalNaN; |
| fflags |= static_cast<uint32_t>( |
| mpact::sim::riscv::FPExceptions::kInvalidOp); |
| } else { |
| result = FpConversionsTestHelper(std::sqrt(input_double_f)) |
| .ConvertWithFlags<HalfFP>( |
| fflags, static_cast<FPRoundingMode>(rm)); |
| } |
| return std::make_tuple(result, fflags); |
| }); |
| } |
| |
| // Test conversion from signed 32 bit integer to half precision. |
| TEST_F(RV32ZfhInstructionTest, RiscVZfhCvtHw) { |
| SetSemanticFunction(&RiscVZfhCvtHw); |
| CvtHwHelper<int32_t>("fcvt.h.w"); |
| } |
| |
| // Test conversion from unsigned 32 bit integer to half precision. |
| TEST_F(RV32ZfhInstructionTest, RiscVZfhCvtHwu) { |
| SetSemanticFunction(&RiscVZfhCvtHwu); |
| CvtHwHelper<uint32_t>("fcvt.h.wu"); |
| } |
| |
| // Test conversion from half precision to signed 32 bit integer. |
| TEST_F(RV32ZfhInstructionTest, RiscVZfhCvtWh) { |
| SetSemanticFunction(&::mpact::sim::riscv::RV32::RiscVZfhCvtWh); |
| CvtWhHelper<int32_t>("fcvt.w.h"); |
| } |
| |
| // Test conversion from half precision to unsigned 32 bit integer. |
| TEST_F(RV32ZfhInstructionTest, RiscVZfhCvtWuh) { |
| SetSemanticFunction(&::mpact::sim::riscv::RV32::RiscVZfhCvtWuh); |
| CvtWhHelper<uint32_t>("fcvt.wu.h"); |
| } |
| |
| // Test equality comparison for half precision values. |
| TEST_F(RV32ZfhInstructionTest, RiscVZfhFcmpeq) { |
| SetSemanticFunction(&::mpact::sim::riscv::RV32::RiscVZfhFcmpeq); |
| CmpEqHelper(); |
| } |
| |
| // Test less than comparison for half precision values. |
| TEST_F(RV32ZfhInstructionTest, RiscVZfhFcmplt) { |
| SetSemanticFunction(&::mpact::sim::riscv::RV32::RiscVZfhFcmplt); |
| CmpLtHelper(); |
| } |
| |
| // Test less than or equal to comparison for half precision values. |
| TEST_F(RV32ZfhInstructionTest, RiscVZfhFcmple) { |
| SetSemanticFunction(&::mpact::sim::riscv::RV32::RiscVZfhFcmple); |
| CmpLeHelper(); |
| } |
| |
| // Test classification of half precision values. |
| TEST_F(RV32ZfhInstructionTest, RiscVZfhFclass) { |
| SetSemanticFunction(&::mpact::sim::riscv::RV32::RiscVZfhFclass); |
| ClassHelper(); |
| } |
| |
| // Test fused multiply add for half precision values. |
| TEST_F(RV32ZfhInstructionTest, RiscVZfhFmadd) { |
| SetSemanticFunction(&RiscVZfhFmadd); |
| TernaryOpWithFflagsFPTestHelper<HalfFP, HalfFP, HalfFP, HalfFP>( |
| "fmadd.h", instruction_, {"f", "f", "f", "f"}, 10, |
| [this](HalfFP a, HalfFP b, HalfFP c) -> std::tuple<HalfFP, uint32_t> { |
| FPRoundingMode rm = rv_fp_->GetRoundingMode(); |
| uint32_t fflags = 0; |
| double a_f = |
| FpConversionsTestHelper(a).ConvertWithFlags<double>(fflags); |
| double b_f = |
| FpConversionsTestHelper(b).ConvertWithFlags<double>(fflags); |
| double c_f = |
| FpConversionsTestHelper(c).ConvertWithFlags<double>(fflags); |
| // Don't collect any host flags from the product operation. |
| double product_f = a_f * b_f; |
| HalfFP result; |
| if ((std::isinf(a_f) && b_f == 0) || (std::isinf(b_f) && a_f == 0)) { |
| fflags |= *FPExceptions::kInvalidOp; |
| result.value = FPTypeInfo<HalfFP>::kCanonicalNaN; |
| } else if (std::isinf(product_f) && std::isinf(c_f) && |
| (std::signbit(product_f) != std::signbit(c_f))) { |
| fflags |= *FPExceptions::kInvalidOp; |
| result.value = FPTypeInfo<HalfFP>::kCanonicalNaN; |
| } else { |
| double result_f; |
| fflags |= GetOperationFlags( |
| [&result_f, &product_f, &c_f] { result_f = product_f + c_f; }); |
| result = FpConversionsTestHelper(result_f).ConvertWithFlags<HalfFP>( |
| fflags, rm); |
| } |
| return std::make_tuple(result, fflags); |
| }); |
| } |
| |
| // Test fused multiply subtract for half precision values. |
| TEST_F(RV32ZfhInstructionTest, RiscVZfhFmsub) { |
| SetSemanticFunction(&RiscVZfhFmsub); |
| TernaryOpWithFflagsFPTestHelper<HalfFP, HalfFP, HalfFP, HalfFP>( |
| "fmsub.h", instruction_, {"f", "f", "f", "f"}, 10, |
| [this](HalfFP a, HalfFP b, HalfFP c) -> std::tuple<HalfFP, uint32_t> { |
| FPRoundingMode rm = rv_fp_->GetRoundingMode(); |
| uint32_t fflags = 0; |
| double a_f = |
| FpConversionsTestHelper(a).ConvertWithFlags<double>(fflags); |
| double b_f = |
| FpConversionsTestHelper(b).ConvertWithFlags<double>(fflags); |
| double c_f = |
| FpConversionsTestHelper(c).ConvertWithFlags<double>(fflags); |
| // Don't collect any host flags from the product operation. |
| double product_f = a_f * b_f; |
| HalfFP result; |
| if ((std::isinf(a_f) && b_f == 0) || (std::isinf(b_f) && a_f == 0)) { |
| fflags |= *FPExceptions::kInvalidOp; |
| result.value = FPTypeInfo<HalfFP>::kCanonicalNaN; |
| } else if (std::isinf(product_f) && std::isinf(c_f) && |
| (std::signbit(product_f) == std::signbit(c_f))) { |
| fflags |= *FPExceptions::kInvalidOp; |
| result.value = FPTypeInfo<HalfFP>::kCanonicalNaN; |
| } else { |
| double result_f; |
| fflags |= GetOperationFlags( |
| [&result_f, &product_f, &c_f] { result_f = product_f - c_f; }); |
| result = FpConversionsTestHelper(result_f).ConvertWithFlags<HalfFP>( |
| fflags, rm); |
| } |
| return std::make_tuple(result, fflags); |
| }); |
| } |
| |
| // Test negative fused multiply add for half precision values. |
| TEST_F(RV32ZfhInstructionTest, RiscVZfhFnmadd) { |
| SetSemanticFunction(&RiscVZfhFnmadd); |
| TernaryOpWithFflagsFPTestHelper<HalfFP, HalfFP, HalfFP, HalfFP>( |
| "fnmadd.h", instruction_, {"f", "f", "f", "f"}, 10, |
| [this](HalfFP a, HalfFP b, HalfFP c) -> std::tuple<HalfFP, uint32_t> { |
| FPRoundingMode rm = rv_fp_->GetRoundingMode(); |
| uint32_t fflags = 0; |
| double a_f = |
| FpConversionsTestHelper(a).ConvertWithFlags<double>(fflags); |
| double b_f = |
| FpConversionsTestHelper(b).ConvertWithFlags<double>(fflags); |
| double c_f = |
| FpConversionsTestHelper(c).ConvertWithFlags<double>(fflags); |
| // Don't collect any host flags from the product operation. |
| double product_f = -(a_f * b_f); |
| HalfFP result; |
| if ((std::isinf(a_f) && b_f == 0) || (std::isinf(b_f) && a_f == 0)) { |
| fflags |= *FPExceptions::kInvalidOp; |
| result.value = FPTypeInfo<HalfFP>::kCanonicalNaN; |
| } else if (std::isinf(product_f) && std::isinf(c_f) && |
| (std::signbit(product_f) == std::signbit(c_f))) { |
| fflags |= *FPExceptions::kInvalidOp; |
| result.value = FPTypeInfo<HalfFP>::kCanonicalNaN; |
| } else { |
| double result_f; |
| fflags |= GetOperationFlags( |
| [&result_f, &product_f, &c_f] { result_f = product_f - c_f; }); |
| result = FpConversionsTestHelper(result_f).ConvertWithFlags<HalfFP>( |
| fflags, rm); |
| } |
| return std::make_tuple(result, fflags); |
| }); |
| } |
| |
| // Test negative fused multiply subtract for half precision values. |
| TEST_F(RV32ZfhInstructionTest, RiscVZfhFnmsub) { |
| SetSemanticFunction(&RiscVZfhFnmsub); |
| TernaryOpWithFflagsFPTestHelper<HalfFP, HalfFP, HalfFP, HalfFP>( |
| "fnmsub.h", instruction_, {"f", "f", "f", "f"}, 10, |
| [this](HalfFP a, HalfFP b, HalfFP c) -> std::tuple<HalfFP, uint32_t> { |
| FPRoundingMode rm = rv_fp_->GetRoundingMode(); |
| uint32_t fflags = 0; |
| double a_f = |
| FpConversionsTestHelper(a).ConvertWithFlags<double>(fflags); |
| double b_f = |
| FpConversionsTestHelper(b).ConvertWithFlags<double>(fflags); |
| double c_f = |
| FpConversionsTestHelper(c).ConvertWithFlags<double>(fflags); |
| // Don't collect any host flags from the product operation. |
| double product_f = -(a_f * b_f); |
| HalfFP result; |
| if ((std::isinf(a_f) && b_f == 0) || (std::isinf(b_f) && a_f == 0)) { |
| fflags |= *FPExceptions::kInvalidOp; |
| result.value = FPTypeInfo<HalfFP>::kCanonicalNaN; |
| } else if (std::isinf(product_f) && std::isinf(c_f) && |
| (std::signbit(product_f) != std::signbit(c_f))) { |
| fflags |= *FPExceptions::kInvalidOp; |
| result.value = FPTypeInfo<HalfFP>::kCanonicalNaN; |
| } else { |
| double result_f; |
| fflags |= GetOperationFlags( |
| [&result_f, &product_f, &c_f] { result_f = product_f + c_f; }); |
| result = FpConversionsTestHelper(result_f).ConvertWithFlags<HalfFP>( |
| fflags, rm); |
| } |
| return std::make_tuple(result, fflags); |
| }); |
| } |
| |
| class RV64ZfhInstructionTest : public RVZfhInstructionTestBase<RV64Register> {}; |
| |
| // 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); |
| FmvXhHelper(); |
| } |
| |
| // Move half precision from an integer register (lower 16 bits) to a float |
| // register. |
| TEST_F(RV64ZfhInstructionTest, RiscVZfhFMvhx) { |
| SetSemanticFunction(&::mpact::sim::riscv::RV64::RiscVZfhFMvhx); |
| UnaryOpFPTestHelper<HalfFP, uint64_t>( |
| "fmv.h.x", instruction_, {"x", "f"}, 32, [](uint64_t scalar) -> HalfFP { |
| return HalfFP{.value = static_cast<uint16_t>(scalar)}; |
| }); |
| } |
| |
| // Move half precision from an integer register to a float register. Confirm |
| // that the destination value is NaN boxed. |
| TEST_F(RV64ZfhInstructionTest, RiscVZfhFMvhx_float_nanbox) { |
| SetSemanticFunction(&::mpact::sim::riscv::RV64::RiscVZfhFMvhx); |
| FmvHxNanBoxHelper<float>(); |
| } |
| |
| // Move half precision from an integer register to a float register. Confirm |
| // that the destination value is NaN boxed. |
| TEST_F(RV64ZfhInstructionTest, RiscVZfhFMvhx_double_nanbox) { |
| SetSemanticFunction(&::mpact::sim::riscv::RV64::RiscVZfhFMvhx); |
| FmvHxNanBoxHelper<double>(); |
| } |
| |
| // Test the FP16 load instruction. The semantic functions should match the isa |
| // file. |
| TEST_F(RV64ZfhInstructionTest, RiscVFlh) { |
| SetSemanticFunction(&::mpact::sim::riscv::RV64::RiscVILhu); |
| SetChildInstruction(); |
| SetChildSemanticFunction(&RiscVZfhFlhChild); |
| |
| SetupMemory<uint64_t, uint16_t>(0xFF, 0xBEEF); |
| |
| HalfFP observed_val = |
| LoadHalfHelper<HalfFP>(/* 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(&::mpact::sim::riscv::RV64::RiscVILhu); |
| SetChildInstruction(); |
| SetChildSemanticFunction(&RiscVZfhFlhChild); |
| |
| SetupMemory<uint64_t, uint16_t>(0xFF, 0xBEEF); |
| |
| float observed_val = LoadHalfHelper<float>(/* 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(&::mpact::sim::riscv::RV64::RiscVILhu); |
| SetChildInstruction(); |
| SetChildSemanticFunction(&RiscVZfhFlhChild); |
| |
| SetupMemory<uint64_t, uint16_t>(0xFF, 0xBEEF); |
| |
| double observed_val = LoadHalfHelper<double>( |
| /* base */ 0x0100, /* offset */ -1); |
| EXPECT_TRUE(std::isnan(observed_val)); |
| } |
| |
| // Test conversion from signed 32 bit integer to half precision. |
| TEST_F(RV64ZfhInstructionTest, RiscVZfhCvtHw) { |
| SetSemanticFunction(&RiscVZfhCvtHw); |
| CvtHwHelper<int32_t>("fcvt.h.w"); |
| } |
| |
| // Test conversion from unsigned 32 bit integer to half precision. |
| TEST_F(RV64ZfhInstructionTest, RiscVZfhCvtHwu) { |
| SetSemanticFunction(&RiscVZfhCvtHwu); |
| CvtHwHelper<uint32_t>("fcvt.h.wu"); |
| } |
| |
| // Test conversion from half precision to signed 32 bit integer. |
| TEST_F(RV64ZfhInstructionTest, RiscVZfhCvtWh) { |
| SetSemanticFunction(&::mpact::sim::riscv::RV64::RiscVZfhCvtWh); |
| CvtWhHelper<int32_t>("fcvt.w.h"); |
| } |
| |
| // Test conversion from half precision to unsigned 32 bit integer. |
| TEST_F(RV64ZfhInstructionTest, RiscVZfhCvtWuh) { |
| SetSemanticFunction(&::mpact::sim::riscv::RV64::RiscVZfhCvtWuh); |
| CvtWhHelper<uint32_t>("fcvt.wu.h"); |
| } |
| |
| // Test equality comparison for half precision values. |
| TEST_F(RV64ZfhInstructionTest, RiscVZfhFcmpeq) { |
| SetSemanticFunction(&::mpact::sim::riscv::RV64::RiscVZfhFcmpeq); |
| CmpEqHelper(); |
| } |
| |
| // Test less than comparison for half precision values. |
| TEST_F(RV64ZfhInstructionTest, RiscVZfhFcmplt) { |
| SetSemanticFunction(&::mpact::sim::riscv::RV64::RiscVZfhFcmplt); |
| CmpLtHelper(); |
| } |
| |
| // Test less than or equal to comparison for half precision values. |
| TEST_F(RV64ZfhInstructionTest, RiscVZfhFcmple) { |
| SetSemanticFunction(&::mpact::sim::riscv::RV64::RiscVZfhFcmple); |
| CmpLeHelper(); |
| } |
| |
| // Test classification of half precision values. |
| TEST_F(RV64ZfhInstructionTest, RiscVZfhFclass) { |
| SetSemanticFunction(&::mpact::sim::riscv::RV64::RiscVZfhFclass); |
| ClassHelper(); |
| } |
| |
| // Test conversion from half precision to signed 64 bit integer. |
| TEST_F(RV64ZfhInstructionTest, RiscVZfhCvtLh) { |
| SetSemanticFunction(&RiscVZfhCvtLh); |
| UnaryOpWithFflagsMixedTestHelper<RV64Register, RVFpRegister, int64_t, HalfFP>( |
| "fcvt.l.h", instruction_, {"f", "x"}, 32, |
| [this](HalfFP input, uint32_t rm) -> std::tuple<int64_t, uint32_t> { |
| uint32_t fflags = 0; |
| double input_double = |
| FpConversionsTestHelper(input).ConvertWithFlags<double>(fflags); |
| int64_t val = |
| this->RoundToInteger<double, int64_t>(input_double, rm, fflags); |
| return std::tuple(val, fflags); |
| }); |
| } |
| |
| // Test conversion from half precision to unsigned 64 bit integer. |
| TEST_F(RV64ZfhInstructionTest, RiscVZfhCvtLuh) { |
| SetSemanticFunction(&RiscVZfhCvtLuh); |
| UnaryOpWithFflagsMixedTestHelper<RV64Register, RVFpRegister, uint64_t, |
| HalfFP>( |
| "fcvt.lu.h", instruction_, {"f", "x"}, 32, |
| [this](HalfFP input, uint32_t rm) -> std::tuple<uint64_t, uint32_t> { |
| uint32_t fflags = 0; |
| double input_double = |
| FpConversionsTestHelper(input).ConvertWithFlags<double>(fflags); |
| uint64_t val = |
| this->RoundToInteger<double, uint64_t>(input_double, rm, fflags); |
| return std::tuple(val, fflags); |
| }); |
| } |
| |
| // Test conversion from signed 64 bit integer to half precision. |
| TEST_F(RV64ZfhInstructionTest, RiscVZfhCvtHl) { |
| SetSemanticFunction(&RiscVZfhCvtHl); |
| UnaryOpWithFflagsMixedTestHelper<RVFpRegister, RV64Register, HalfFP, int64_t>( |
| "fcvt.h.l", instruction_, {"x", "f"}, 32, |
| [](int64_t input_int, uint32_t rm) -> std::tuple<HalfFP, uint32_t> { |
| uint32_t fflags = 0; |
| HalfFP result = FpConversionsTestHelper(static_cast<double>(input_int)) |
| .ConvertWithFlags<HalfFP>( |
| fflags, static_cast<FPRoundingMode>(rm)); |
| return std::tuple(result, fflags); |
| }); |
| } |
| |
| // Test conversion from unsigned 64 bit integer to half precision. |
| TEST_F(RV64ZfhInstructionTest, RiscVZfhCvtHlu) { |
| SetSemanticFunction(&RiscVZfhCvtHlu); |
| UnaryOpWithFflagsMixedTestHelper<RVFpRegister, RV64Register, HalfFP, |
| uint64_t>( |
| "fcvt.h.lu", instruction_, {"x", "f"}, 32, |
| [](uint64_t input_int, uint32_t rm) -> std::tuple<HalfFP, uint32_t> { |
| uint32_t fflags = 0; |
| HalfFP result = FpConversionsTestHelper(static_cast<double>(input_int)) |
| .ConvertWithFlags<HalfFP>( |
| fflags, static_cast<FPRoundingMode>(rm)); |
| return std::tuple(result, fflags); |
| }); |
| } |
| |
| } // namespace |