| // Copyright 2023 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #ifndef MPACT_RISCV_RISCV_TEST_RISCV_FP_TEST_BASE_H_ |
| #define MPACT_RISCV_RISCV_TEST_RISCV_FP_TEST_BASE_H_ |
| |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| |
| #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/log/log.h" |
| #include "absl/random/random.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/instruction.h" |
| #include "mpact/sim/generic/type_helpers.h" |
| #include "mpact/sim/util/memory/flat_demand_memory.h" |
| #include "riscv/riscv_fp_host.h" |
| #include "riscv/riscv_fp_info.h" |
| #include "riscv/riscv_fp_state.h" |
| #include "riscv/riscv_register.h" |
| #include "riscv/riscv_state.h" |
| |
| namespace mpact { |
| namespace sim { |
| namespace riscv { |
| namespace test { |
| |
| using ::mpact::sim::generic::ConvertHalfToSingle; |
| using ::mpact::sim::generic::FloatingPointToString; |
| using ::mpact::sim::generic::HalfFP; |
| using ::mpact::sim::generic::Instruction; |
| using ::mpact::sim::generic::IsMpactFp; |
| using ::mpact::sim::riscv::FPRoundingMode; |
| using ::mpact::sim::util::FlatDemandMemory; |
| |
| constexpr int kTestValueLength = 256; |
| |
| constexpr uint32_t kInstAddress = 0x1000; |
| constexpr uint32_t kDataLoadAddress = 0x1'0000; |
| constexpr uint32_t kDataStoreAddress = 0x2'0000; |
| |
| // Templated helper structs to provide information about floating point types. |
| template <typename T> |
| struct FPTypeInfo { |
| using IntType = T; |
| static const int kBitSize = 8 * sizeof(T); |
| static const int kExpSize = 0; |
| static const int kSigSize = 0; |
| static bool IsNaN(T value) { return false; } |
| static constexpr IntType kQNaN = 0; |
| static constexpr IntType kSNaN = 0; |
| static constexpr IntType kPosInf = std::numeric_limits<T>::max(); |
| static constexpr IntType kNegInf = std::numeric_limits<T>::min(); |
| static constexpr IntType kPosZero = 0; |
| static constexpr IntType kNegZero = 0; |
| static constexpr IntType kPosDenorm = 0; |
| static constexpr IntType kNegDenorm = 0; |
| }; |
| |
| template <> |
| struct FPTypeInfo<float> { |
| using T = float; |
| using IntType = uint32_t; |
| static const int kExpBias = 127; |
| static const int kBitSize = sizeof(float) << 3; |
| static const int kExpSize = 8; |
| static const int kSigSize = kBitSize - kExpSize - 1; |
| static const IntType kExpMask = ((1ULL << kExpSize) - 1) << kSigSize; |
| static const IntType kSigMask = (1ULL << kSigSize) - 1; |
| static const IntType kQNaN = kExpMask | (1ULL << (kSigSize - 1)) | 1; |
| static const IntType kSNaN = kExpMask | 1; |
| static const IntType kPosInf = kExpMask; |
| static const IntType kNegInf = kExpMask | (1ULL << (kBitSize - 1)); |
| static const IntType kPosZero = 0; |
| static const IntType kNegZero = 1ULL << (kBitSize - 1); |
| static const IntType kPosDenorm = 1ULL << (kSigSize - 2); |
| static const IntType kNegDenorm = |
| (1ULL << (kBitSize - 1)) | (1ULL << (kSigSize - 2)); |
| static const IntType kCanonicalNaN = 0x7fc0'0000ULL; |
| static bool IsNaN(T value) { return std::isnan(value); } |
| static bool IsQNaN(T value) { |
| IntType uint_val = absl::bit_cast<IntType>(value); |
| return IsNaN(value) && (((1ULL << (kSigSize - 1)) & uint_val) != 0); |
| } |
| static bool IsInf(T value) { return std::isinf(value); } |
| }; |
| |
| template <> |
| struct FPTypeInfo<double> { |
| using T = double; |
| using IntType = uint64_t; |
| static const int kExpBias = 1023; |
| static const int kBitSize = sizeof(double) << 3; |
| static const int kExpSize = 11; |
| static const int kSigSize = kBitSize - kExpSize - 1; |
| static const IntType kExpMask = ((1ULL << kExpSize) - 1) << kSigSize; |
| static const IntType kSigMask = (1ULL << kSigSize) - 1; |
| static const IntType kQNaN = kExpMask | (1ULL << (kSigSize - 1)) | 1; |
| static const IntType kSNaN = kExpMask | 1; |
| static const IntType kPosInf = kExpMask; |
| static const IntType kNegInf = kExpMask | (1ULL << (kBitSize - 1)); |
| static const IntType kPosZero = 0; |
| static const IntType kNegZero = 1ULL << (kBitSize - 1); |
| static const IntType kPosDenorm = 1ULL << (kSigSize - 2); |
| static const IntType kNegDenorm = |
| (1ULL << (kBitSize - 1)) | (1ULL << (kSigSize - 2)); |
| static const IntType kCanonicalNaN = 0x7ff8'0000'0000'0000ULL; |
| static bool IsNaN(T value) { return std::isnan(value); } |
| static bool IsQNaN(T value) { |
| IntType uint_val = absl::bit_cast<IntType>(value); |
| return IsNaN(value) && (((1ULL << (kSigSize - 1)) & uint_val) != 0); |
| } |
| static bool IsInf(T value) { return std::isinf(value); } |
| }; |
| |
| template <> |
| struct FPTypeInfo<HalfFP> { |
| using T = HalfFP; |
| using IntType = uint16_t; |
| static const int kExpBias = 15; |
| static const int kBitSize = sizeof(HalfFP) << 3; |
| static const int kExpSize = 5; |
| static const int kSigSize = kBitSize - kExpSize - 1; // 10 from the spec. |
| static const IntType kExpMask = ((1ULL << kExpSize) - 1) << kSigSize; |
| static const IntType kSigMask = (1ULL << kSigSize) - 1; |
| static const IntType kQNaN = kExpMask | (1ULL << (kSigSize - 1)) | 1; |
| static const IntType kSNaN = kExpMask | 1; |
| static const IntType kPosInf = kExpMask; |
| static const IntType kNegInf = kExpMask | (1ULL << (kBitSize - 1)); |
| static const IntType kPosZero = 0; |
| static const IntType kNegZero = 1ULL << (kBitSize - 1); |
| static const IntType kPosDenorm = 1ULL << (kSigSize - 2); |
| static const IntType kNegDenorm = |
| (1ULL << (kBitSize - 1)) | (1ULL << (kSigSize - 2)); |
| static const IntType kCanonicalNaN = 0x7e00; |
| // std::isnan won't work for half precision. |
| static bool IsNaN(T wrapper) { |
| IntType exp = (wrapper.value & kExpMask) >> kSigSize; |
| IntType sig = wrapper.value & kSigMask; |
| return (exp == (1 << kExpSize) - 1) && (sig != 0); |
| } |
| static bool IsQNaN(T value) { |
| IntType uint_val = absl::bit_cast<IntType>(value); |
| IntType significand_msb = (uint_val >> (kSigSize - 1)) & 1; |
| return IsNaN(value) && (significand_msb != 0); |
| } |
| // std::isinf won't work for half precision. |
| static bool IsInf(T wrapper) { |
| IntType exp = (wrapper.value & kExpMask) >> kSigSize; |
| IntType sig = wrapper.value & kSigMask; |
| return (exp == (1 << kExpSize) - 1) && (sig == 0); |
| } |
| }; |
| |
| // Templated helper function for classifying fp numbers. |
| template <typename T> |
| typename FPTypeInfo<T>::IntType VfclassVHelper(T val) { |
| auto fp_class = fpclassify(val); |
| switch (fp_class) { |
| case FP_INFINITE: |
| return std::signbit(val) ? 1 : 1 << 7; |
| case FP_NAN: { |
| auto uint_val = absl::bit_cast<typename FPTypeInfo<T>::IntType>(val); |
| bool quiet_nan = (uint_val >> (FPTypeInfo<T>::kSigSize - 1)) & 1; |
| return quiet_nan ? 1 << 9 : 1 << 8; |
| } |
| case FP_ZERO: |
| return std::signbit(val) ? 1 << 3 : 1 << 4; |
| case FP_SUBNORMAL: |
| return std::signbit(val) ? 1 << 2 : 1 << 5; |
| case FP_NORMAL: |
| return std::signbit(val) ? 1 << 1 : 1 << 6; |
| } |
| return 0; |
| } |
| |
| // These templated functions allow for comparison of values with a tolerance |
| // given for floating point types. The tolerance is stated as the bit position |
| // in the mantissa of the op, with 0 being the msb of the mantissa. If the |
| // bit position is beyond the mantissa, a comparison of equal is performed. |
| template <typename T> |
| inline void FPCompare(T op, T reg, int, absl::string_view str) { |
| EXPECT_EQ(reg, op) << str; |
| } |
| |
| template <> |
| inline void FPCompare<float>(float op, float reg, int delta_position, |
| absl::string_view str) { |
| using T = float; |
| using UInt = typename FPTypeInfo<T>::IntType; |
| UInt u_op = absl::bit_cast<UInt>(op); |
| UInt u_reg = absl::bit_cast<UInt>(reg); |
| if (!std::isnan(op) && !std::isinf(op) && |
| delta_position < FPTypeInfo<T>::kSigSize) { |
| T delta; |
| UInt exp = FPTypeInfo<T>::kExpMask >> FPTypeInfo<T>::kSigSize; |
| if (exp > delta_position) { |
| exp -= delta_position; |
| UInt udelta = exp << FPTypeInfo<T>::kSigSize; |
| delta = absl::bit_cast<T>(udelta); |
| } else { |
| // Becomes a denormal |
| int diff = delta_position - exp; |
| UInt udelta = 1ULL << (FPTypeInfo<T>::kSigSize - 1 - diff); |
| delta = absl::bit_cast<T>(udelta); |
| } |
| EXPECT_THAT(reg, testing::NanSensitiveFloatNear(op, delta)) |
| << str << " op: " << std::hex << u_op << " reg: " << std::hex |
| << u_reg; |
| } else { |
| EXPECT_THAT(reg, testing::NanSensitiveFloatEq(op)) |
| << str << " op: " << std::hex << u_op << " reg: " << std::hex |
| << u_reg; |
| } |
| } |
| |
| template <> |
| inline void FPCompare<double>(double op, double reg, int delta_position, |
| absl::string_view str) { |
| using T = double; |
| using UInt = typename FPTypeInfo<T>::IntType; |
| UInt u_op = absl::bit_cast<UInt>(op); |
| UInt u_reg = absl::bit_cast<UInt>(reg); |
| if (!std::isnan(op) && !std::isinf(op) && |
| delta_position < FPTypeInfo<T>::kSigSize) { |
| T delta; |
| UInt exp = FPTypeInfo<T>::kExpMask >> FPTypeInfo<T>::kSigSize; |
| if (exp > delta_position) { |
| exp -= delta_position; |
| UInt udelta = exp << FPTypeInfo<T>::kSigSize; |
| delta = absl::bit_cast<T>(udelta); |
| } else { |
| // Becomes a denormal |
| int diff = delta_position - exp; |
| UInt udelta = 1ULL << (FPTypeInfo<T>::kSigSize - 1 - diff); |
| delta = absl::bit_cast<T>(udelta); |
| } |
| EXPECT_THAT(reg, testing::NanSensitiveDoubleNear(op, delta)) |
| << str << " op: " << std::hex << u_op << " reg: " << std::hex |
| << u_reg; |
| } else { |
| EXPECT_THAT(reg, testing::NanSensitiveDoubleEq(op)) |
| << str << " op: " << std::hex << u_op << " reg: " << std::hex |
| << u_reg; |
| } |
| } |
| |
| template <> |
| inline void FPCompare<HalfFP>(HalfFP op, HalfFP reg, int delta_position, |
| absl::string_view str) { |
| float op_float = ConvertHalfToSingle(op); |
| float reg_float = ConvertHalfToSingle(reg); |
| FPCompare<float>(op_float, reg_float, delta_position, str); |
| } |
| |
| template <typename FP> |
| FP OptimizationBarrier(FP op) { |
| asm volatile("" : "+X"(op)); |
| return op; |
| } |
| |
| namespace internal { |
| |
| // These are predicates used in the following NaNBox function definitions, as |
| // part of the enable_if construct. |
| template <typename S, typename D> |
| struct EqualSize { |
| static const bool value = sizeof(S) == sizeof(D) && IsMpactFp<S>::value && |
| std::is_integral<D>::value; |
| }; |
| |
| template <typename S, typename D> |
| struct GreaterSize { |
| static const bool value = |
| sizeof(S) > sizeof(D) && IsMpactFp<S>::value &&std::is_integral<D>::value; |
| }; |
| |
| template <typename S, typename D> |
| struct LessSize { |
| static const bool value = sizeof(S) < sizeof(D) && IsMpactFp<S>::value && |
| std::is_integral<D>::value; |
| }; |
| |
| } // namespace internal |
| |
| // Template functions to NaN box a floating point value when being assigned |
| // to a wider register. The first version places a smaller floating point value |
| // in a NaN box (all upper bits in the word are set to 1). |
| |
| // Enable_if is used to select the proper implementation for different S and D |
| // type combinations. It uses the SFINAE (substitution failure is not an error) |
| // "feature" of C++ to hide the implementation that don't match the predicate |
| // from being resolved. |
| |
| template <typename S, typename D> |
| inline typename std::enable_if<internal::LessSize<S, D>::value, D>::type NaNBox( |
| S value) { |
| using SInt = typename FPTypeInfo<S>::IntType; |
| SInt sval = absl::bit_cast<SInt>(value); |
| D dval = (~static_cast<D>(0) << (sizeof(S) * 8)) | sval; |
| return absl::bit_cast<D>(dval); |
| } |
| |
| // This version does a straight copy - as the data types are the same size. |
| template <typename S, typename D> |
| inline typename std::enable_if<internal::EqualSize<S, D>::value, D>::type |
| NaNBox(S value) { |
| return absl::bit_cast<D>(value); |
| } |
| |
| // Signal error if the register is smaller than the floating point value. |
| template <typename S, typename D> |
| inline typename std::enable_if<internal::GreaterSize<S, D>::value, D>::type |
| NaNBox(S value) { |
| // No return statement, so error will be reported. |
| } |
| |
| class RiscVFPInstructionTestBase : public testing::Test { |
| public: |
| RiscVFPInstructionTestBase() { |
| memory_ = new FlatDemandMemory(0); |
| state_ = new RiscVState("test", RiscVXlen::RV32, memory_); |
| rv_fp_ = new mpact::sim::riscv::RiscVFPState(state_->csr_set(), state_); |
| state_->set_rv_fp(rv_fp_); |
| instruction_ = new Instruction(kInstAddress, state_); |
| instruction_->set_size(4); |
| child_instruction_ = new Instruction(kInstAddress, state_); |
| child_instruction_->set_size(4); |
| // Initialize a portion of memory with a known pattern. |
| auto *db = state_->db_factory()->Allocate(8192); |
| auto span = db->Get<uint8_t>(); |
| for (int i = 0; i < 8192; i++) { |
| span[i] = i & 0xff; |
| } |
| memory_->Store(kDataLoadAddress - 4096, db); |
| db->DecRef(); |
| for (int i = 1; i < 32; i++) { |
| xreg_[i] = state_->GetRegister<RV32Register>(absl::StrCat("x", i)).first; |
| } |
| for (int i = 1; i < 32; i++) { |
| freg_[i] = state_->GetRegister<RVFpRegister>(absl::StrCat("f", i)).first; |
| } |
| for (int i = 1; i < 32; i++) { |
| dreg_[i] = state_->GetRegister<RVFpRegister>(absl::StrCat("d", i)).first; |
| } |
| } |
| ~RiscVFPInstructionTestBase() override { |
| delete rv_fp_; |
| state_->set_rv_fp(nullptr); |
| delete state_; |
| instruction_->DecRef(); |
| child_instruction_->DecRef(); |
| delete memory_; |
| } |
| |
| // Clear the instruction instance and allocate a new one. |
| void ResetInstruction() { |
| instruction_->DecRef(); |
| instruction_ = new Instruction(kInstAddress, state_); |
| instruction_->set_size(4); |
| } |
| |
| // Creates source and destination scalar register operands for the registers |
| // named in the two vectors and append them to the given instruction. |
| template <typename T> |
| void AppendRegisterOperands(Instruction *inst, |
| const std::vector<std::string> &sources, |
| const std::vector<std::string> &destinations) { |
| for (auto ®_name : sources) { |
| auto *reg = state_->GetRegister<T>(reg_name).first; |
| inst->AppendSource(reg->CreateSourceOperand()); |
| } |
| for (auto ®_name : destinations) { |
| auto *reg = state_->GetRegister<T>(reg_name).first; |
| inst->AppendDestination(reg->CreateDestinationOperand(0)); |
| } |
| } |
| |
| // Creates source and destination scalar register operands for the registers |
| // named in the two vectors and append them to the default instruction. |
| template <typename T> |
| void AppendRegisterOperands(const std::vector<std::string> &sources, |
| const std::vector<std::string> &destinations) { |
| AppendRegisterOperands<T>(instruction_, sources, destinations); |
| } |
| |
| // named register and sets it to the corresponding value. |
| template <typename T, typename RegisterType = RV32Register> |
| void SetRegisterValues( |
| const std::vector<std::tuple<std::string, const T>> &values) { |
| for (auto &[reg_name, value] : values) { |
| auto *reg = state_->GetRegister<RegisterType>(reg_name).first; |
| auto *db = |
| state_->db_factory()->Allocate<typename RegisterType::ValueType>(1); |
| db->template Set<T>(0, value); |
| reg->SetDataBuffer(db); |
| db->DecRef(); |
| } |
| } |
| |
| template <typename T, typename RegisterType = RV32Register> |
| void SetNaNBoxedRegisterValues( |
| const std::vector<std::tuple<std::string, const T>> &values) { |
| for (auto &[reg_name, value] : values) { |
| typename RegisterType::ValueType reg_value = |
| NaNBox<T, typename RegisterType::ValueType>(value); |
| auto *reg = state_->GetRegister<RegisterType>(reg_name).first; |
| auto *db = |
| state_->db_factory()->Allocate<typename RegisterType::ValueType>(1); |
| db->template Set<typename RegisterType::ValueType>(0, reg_value); |
| reg->SetDataBuffer(db); |
| db->DecRef(); |
| } |
| } |
| |
| // Initializes the semantic function of the instruction object. |
| void SetSemanticFunction(Instruction *inst, |
| Instruction::SemanticFunction fcn) { |
| inst->set_semantic_function(fcn); |
| } |
| |
| // Initializes the semantic function for the default instruction. |
| void SetSemanticFunction(Instruction::SemanticFunction fcn) { |
| instruction_->set_semantic_function(fcn); |
| } |
| |
| // Sets the default child instruction as the child of the default instruction. |
| void SetChildInstruction() { instruction_->AppendChild(child_instruction_); } |
| |
| // Initializes the semantic function for the default child instruction. |
| void SetChildSemanticFunction(Instruction::SemanticFunction fcn) { |
| child_instruction_->set_semantic_function(fcn); |
| } |
| |
| // Construct a random FP value by separately generating integer values for |
| // sign, exponent and mantissa. |
| template <typename T> |
| T RandomFPValue() { |
| using UInt = typename FPTypeInfo<T>::IntType; |
| UInt sign = absl::Uniform(absl::IntervalClosed, bitgen_, 0ULL, 1ULL); |
| UInt exp = absl::Uniform(absl::IntervalClosedOpen, bitgen_, 0ULL, |
| 1ULL << FPTypeInfo<T>::kExpSize); |
| UInt sig = absl::Uniform(absl::IntervalClosedOpen, bitgen_, 0ULL, |
| 1ULL << FPTypeInfo<T>::kSigSize); |
| UInt value = (sign & 1) << (FPTypeInfo<T>::kBitSize - 1) | |
| (exp << FPTypeInfo<T>::kSigSize) | sig; |
| return absl::bit_cast<T>(value); |
| ; |
| } |
| |
| // This method uses random values for each field in the fp number. |
| template <typename T> |
| void FillArrayWithRandomFPValues(absl::Span<T> span) { |
| for (auto &val : span) { |
| val = RandomFPValue<T>(); |
| } |
| } |
| |
| template <typename R, typename LHS> |
| void UnaryOpFPTestHelper(absl::string_view name, Instruction *inst, |
| absl::Span<const absl::string_view> reg_prefixes, |
| int delta_position, |
| std::function<R(LHS)> operation) { |
| using LhsRegisterType = RVFpRegister; |
| using DestRegisterType = RVFpRegister; |
| 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') { |
| AppendRegisterOperands<RV32Register>({kR1Name}, {}); |
| } else { |
| AppendRegisterOperands<RVFpRegister>({kR1Name}, {}); |
| } |
| if (kRdName[0] == 'x') { |
| AppendRegisterOperands<RV32Register>({}, {kRdName}); |
| } else { |
| AppendRegisterOperands<RVFpRegister>({}, {kRdName}); |
| } |
| AppendRegisterOperands<RV32Register>({kRmName}, {}); |
| FillArrayWithRandomFPValues<LHS>(lhs_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; |
| for (int i = 0; i < kTestValueLength; i++) { |
| if constexpr (std::is_integral<LHS>::value) { |
| SetRegisterValues<LHS, LhsRegisterType>({{kR1Name, lhs_span[i]}}); |
| } else { |
| SetNaNBoxedRegisterValues<LHS, LhsRegisterType>( |
| {{kR1Name, lhs_span[i]}}); |
| } |
| |
| for (int rm : {0, 1, 2, 3, 4}) { |
| rv_fp_->SetRoundingMode(static_cast<FPRoundingMode>(rm)); |
| SetRegisterValues<int, RV32Register>({{kRmName, rm}}); |
| SetRegisterValues<DestRegisterType::ValueType, DestRegisterType>( |
| {{kRdName, 0}}); |
| |
| inst->Execute(nullptr); |
| |
| R op_val; |
| { |
| ScopedFPStatus set_fpstatus(rv_fp_->host_fp_interface()); |
| op_val = operation(lhs_span[i]); |
| } |
| auto reg_val = state_->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]))); |
| } |
| } |
| } |
| |
| // Tester for unary instructions that produce an exception flag value. |
| template <typename R, typename LHS> |
| void UnaryOpWithFflagsFPTestHelper( |
| 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 LhsRegisterType = RVFpRegister; |
| using DestRegisterType = RVFpRegister; |
| 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') { |
| AppendRegisterOperands<RV32Register>({kR1Name}, {}); |
| } else { |
| AppendRegisterOperands<RVFpRegister>({kR1Name}, {}); |
| } |
| if (kRdName[0] == 'x') { |
| AppendRegisterOperands<RV32Register>({}, {kRdName}); |
| } else { |
| AppendRegisterOperands<RVFpRegister>({}, {kRdName}); |
| } |
| AppendRegisterOperands<RV32Register>({kRmName}, {}); |
| auto *flag_op = rv_fp_->fflags()->CreateSetDestinationOperand(0, "fflags"); |
| instruction_->AppendDestination(flag_op); |
| 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) { |
| SetRegisterValues<LHS, LhsRegisterType>({{kR1Name, lhs_span[i]}}); |
| } else { |
| SetNaNBoxedRegisterValues<LHS, LhsRegisterType>( |
| {{kR1Name, lhs_span[i]}}); |
| } |
| |
| for (int rm : {0, 1, 2, 3, 4}) { |
| rv_fp_->SetRoundingMode(static_cast<FPRoundingMode>(rm)); |
| rv_fp_->fflags()->Write(static_cast<uint32_t>(0)); |
| SetRegisterValues<int, RV32Register>({{kRmName, rm}, {}}); |
| SetRegisterValues<DestRegisterType::ValueType, DestRegisterType>( |
| {{kRdName, 0}}); |
| |
| inst->Execute(nullptr); |
| auto instruction_fflags = rv_fp_->fflags()->GetUint32(); |
| |
| R op_val; |
| uint32_t test_operation_fflags; |
| { |
| ScopedFPRoundingMode scoped_rm(rv_fp_->host_fp_interface(), rm); |
| std::tie(op_val, test_operation_fflags) = operation(lhs_span[i], rm); |
| } |
| |
| auto reg_val = state_->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; |
| } |
| } |
| } |
| |
| // Test helper for binary fp instructions. |
| template <typename R, typename LHS, typename RHS> |
| void BinaryOpFPTestHelper(absl::string_view name, Instruction *inst, |
| absl::Span<const absl::string_view> reg_prefixes, |
| int delta_position, |
| std::function<R(LHS, RHS)> operation) { |
| using LhsRegisterType = RVFpRegister; |
| using RhsRegisterType = RVFpRegister; |
| using DestRegisterType = RVFpRegister; |
| LHS lhs_values[kTestValueLength]; |
| RHS rhs_values[kTestValueLength]; |
| auto lhs_span = absl::Span<LHS>(lhs_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 kRdName = absl::StrCat(reg_prefixes[2], 5); |
| // This is used for the rounding mode operand. |
| const std::string kRmName = absl::StrCat("x", 10); |
| if (kR1Name[0] == 'x') { |
| AppendRegisterOperands<RV32Register>({kR1Name}, {}); |
| } else { |
| AppendRegisterOperands<RVFpRegister>({kR1Name}, {}); |
| } |
| if (kR2Name[0] == 'x') { |
| AppendRegisterOperands<RV32Register>({kR2Name}, {}); |
| } else { |
| AppendRegisterOperands<RVFpRegister>({kR2Name}, {}); |
| } |
| if (kRdName[0] == 'x') { |
| AppendRegisterOperands<RV32Register>({}, {kRdName}); |
| } else { |
| AppendRegisterOperands<RVFpRegister>({}, {kRdName}); |
| } |
| AppendRegisterOperands<RV32Register>({kRmName}, {}); |
| auto *flag_op = rv_fp_->fflags()->CreateSetDestinationOperand(0, "fflags"); |
| instruction_->AppendDestination(flag_op); |
| FillArrayWithRandomFPValues<LHS>(lhs_span); |
| 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; |
| for (int i = 0; i < kTestValueLength; i++) { |
| SetNaNBoxedRegisterValues<LHS, LhsRegisterType>({{kR1Name, lhs_span[i]}}); |
| SetNaNBoxedRegisterValues<RHS, RhsRegisterType>({{kR2Name, rhs_span[i]}}); |
| |
| for (int rm : {0, 1, 2, 3, 4}) { |
| rv_fp_->SetRoundingMode(static_cast<FPRoundingMode>(rm)); |
| SetRegisterValues<int, RV32Register>({{kRmName, rm}}); |
| SetRegisterValues<R, DestRegisterType>({{kRdName, 0}}); |
| |
| inst->Execute(nullptr); |
| |
| R op_val; |
| { |
| ScopedFPStatus set_fpstatus(rv_fp_->host_fp_interface()); |
| op_val = operation(lhs_span[i], rhs_span[i]); |
| } |
| auto reg_val = state_->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]), " ", |
| FloatingPointToString<RHS>(rhs_span[i]), " rm: ", rm)); |
| } |
| if (HasFailure()) return; |
| } |
| } |
| |
| // Test helper for binary instructions that also produce an exception flag |
| // value. |
| template <typename R, typename LHS, typename RHS> |
| void BinaryOpWithFflagsFPTestHelper( |
| 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, RHS)> operation) { |
| using LhsRegisterType = RVFpRegister; |
| using RhsRegisterType = RVFpRegister; |
| using DestRegisterType = RVFpRegister; |
| using LhsUInt = typename FPTypeInfo<LHS>::IntType; |
| using RhsUInt = typename FPTypeInfo<RHS>::IntType; |
| LHS lhs_values[kTestValueLength]; |
| RHS rhs_values[kTestValueLength]; |
| auto lhs_span = absl::Span<LHS>(lhs_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 kRdName = absl::StrCat(reg_prefixes[2], 5); |
| // This is used for the rounding mode operand. |
| const std::string kRmName = absl::StrCat("x", 10); |
| if (kR1Name[0] == 'x') { |
| AppendRegisterOperands<RV32Register>({kR1Name}, {}); |
| } else { |
| AppendRegisterOperands<RVFpRegister>({kR1Name}, {}); |
| } |
| if (kR2Name[0] == 'x') { |
| AppendRegisterOperands<RV32Register>({kR2Name}, {}); |
| } else { |
| AppendRegisterOperands<RVFpRegister>({kR2Name}, {}); |
| } |
| if (kRdName[0] == 'x') { |
| AppendRegisterOperands<RV32Register>({}, {kRdName}); |
| } else { |
| AppendRegisterOperands<RVFpRegister>({}, {kRdName}); |
| } |
| AppendRegisterOperands<RV32Register>({kRmName}, {}); |
| auto *flag_op = rv_fp_->fflags()->CreateSetDestinationOperand(0, "fflags"); |
| instruction_->AppendDestination(flag_op); |
| FillArrayWithRandomFPValues<LHS>(lhs_span); |
| 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; |
| for (int i = 0; i < kTestValueLength; i++) { |
| SetNaNBoxedRegisterValues<LHS, LhsRegisterType>({{kR1Name, lhs_span[i]}}); |
| SetNaNBoxedRegisterValues<RHS, RhsRegisterType>({{kR2Name, rhs_span[i]}}); |
| |
| for (int rm : {0, 1, 2, 3, 4}) { |
| rv_fp_->SetRoundingMode(static_cast<FPRoundingMode>(rm)); |
| rv_fp_->fflags()->Write(static_cast<uint32_t>(0)); |
| SetRegisterValues<int, RV32Register>({{kRmName, rm}, {}}); |
| SetRegisterValues<R, DestRegisterType>({{kRdName, 0}}); |
| |
| inst->Execute(nullptr); |
| |
| auto instruction_fflags = rv_fp_->fflags()->GetUint32(); |
| |
| R op_val; |
| uint32_t test_operation_fflags; |
| { |
| ScopedFPStatus set_fpstatus(rv_fp_->host_fp_interface()); |
| std::tie(op_val, test_operation_fflags) = |
| operation(lhs_span[i], rhs_span[i]); |
| } |
| auto reg_val = state_->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]), " ", |
| FloatingPointToString<RHS>(rhs_span[i]))); |
| LhsUInt lhs_uint = absl::bit_cast<LhsUInt>(lhs_span[i]); |
| RhsUInt rhs_uint = absl::bit_cast<RhsUInt>(rhs_span[i]); |
| EXPECT_EQ(test_operation_fflags, instruction_fflags) |
| << std::hex << name << "(" << lhs_uint << ", " << rhs_uint << ")"; |
| } |
| } |
| } |
| |
| template <typename R, typename LHS, typename MHS, typename RHS> |
| void TernaryOpFPTestHelper(absl::string_view name, Instruction *inst, |
| absl::Span<const absl::string_view> reg_prefixes, |
| int delta_position, |
| std::function<R(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); |
| // This is used for the rounding mode operand. |
| const std::string kRmName = absl::StrCat("x", 10); |
| if (kR1Name[0] == 'x') { |
| AppendRegisterOperands<RV32Register>({kR1Name}, {}); |
| } else { |
| AppendRegisterOperands<RVFpRegister>({kR1Name}, {}); |
| } |
| if (kR2Name[0] == 'x') { |
| AppendRegisterOperands<RV32Register>({kR2Name}, {}); |
| } else { |
| AppendRegisterOperands<RVFpRegister>({kR2Name}, {}); |
| } |
| if (kR3Name[0] == 'x') { |
| AppendRegisterOperands<RV32Register>({kR3Name}, {}); |
| } else { |
| AppendRegisterOperands<RVFpRegister>({kR3Name}, {}); |
| } |
| if (kRdName[0] == 'x') { |
| AppendRegisterOperands<RV32Register>({}, {kRdName}); |
| } else { |
| AppendRegisterOperands<RVFpRegister>({}, {kRdName}); |
| } |
| AppendRegisterOperands<RV32Register>({kRmName}, {}); |
| FillArrayWithRandomFPValues<LHS>(lhs_span); |
| FillArrayWithRandomFPValues<MHS>(mhs_span); |
| 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; |
| for (int i = 0; i < kTestValueLength; i++) { |
| SetNaNBoxedRegisterValues<LHS, LhsRegisterType>({{kR1Name, lhs_span[i]}}); |
| SetNaNBoxedRegisterValues<MHS, MhsRegisterType>({{kR2Name, mhs_span[i]}}); |
| SetNaNBoxedRegisterValues<RHS, RhsRegisterType>({{kR3Name, rhs_span[i]}}); |
| |
| for (int rm : {0, 1, 2, 3, 4}) { |
| rv_fp_->SetRoundingMode(static_cast<FPRoundingMode>(rm)); |
| SetRegisterValues<int, RV32Register>({{kRmName, rm}}); |
| SetRegisterValues<R, DestRegisterType>({{kRdName, 0}}); |
| |
| inst->Execute(nullptr); |
| |
| R op_val; |
| { |
| ScopedFPStatus set_fpstatus(rv_fp_->host_fp_interface()); |
| op_val = operation(lhs_span[i], mhs_span[i], rhs_span[i]); |
| } |
| auto reg_val = state_->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]), " ", |
| FloatingPointToString<MHS>(mhs_span[i]), " ", |
| FloatingPointToString<RHS>(rhs_span[i]))); |
| } |
| } |
| } |
| |
| 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<R(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); |
| // This is used for the rounding mode operand. |
| const std::string kRmName = absl::StrCat("x", 10); |
| if (kR1Name[0] == 'x') { |
| AppendRegisterOperands<RV32Register>({kR1Name}, {}); |
| } else { |
| AppendRegisterOperands<RVFpRegister>({kR1Name}, {}); |
| } |
| if (kR2Name[0] == 'x') { |
| AppendRegisterOperands<RV32Register>({kR2Name}, {}); |
| } else { |
| AppendRegisterOperands<RVFpRegister>({kR2Name}, {}); |
| } |
| if (kR3Name[0] == 'x') { |
| AppendRegisterOperands<RV32Register>({kR3Name}, {}); |
| } else { |
| AppendRegisterOperands<RVFpRegister>({kR3Name}, {}); |
| } |
| if (kRdName[0] == 'x') { |
| AppendRegisterOperands<RV32Register>({}, {kRdName}); |
| } else { |
| AppendRegisterOperands<RVFpRegister>({}, {kRdName}); |
| } |
| AppendRegisterOperands<RV32Register>({kRmName}, {}); |
| auto *flag_op = rv_fp_->fflags()->CreateSetDestinationOperand(0, "fflags"); |
| instruction_->AppendDestination(flag_op); |
| FillArrayWithRandomFPValues<LHS>(lhs_span); |
| FillArrayWithRandomFPValues<MHS>(mhs_span); |
| 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; |
| for (int i = 0; i < kTestValueLength; i++) { |
| SetNaNBoxedRegisterValues<LHS, LhsRegisterType>({{kR1Name, lhs_span[i]}}); |
| SetNaNBoxedRegisterValues<MHS, MhsRegisterType>({{kR2Name, mhs_span[i]}}); |
| SetNaNBoxedRegisterValues<RHS, RhsRegisterType>({{kR3Name, rhs_span[i]}}); |
| |
| for (int rm : {0, 1, 2, 3, 4}) { |
| rv_fp_->SetRoundingMode(static_cast<FPRoundingMode>(rm)); |
| rv_fp_->fflags()->Write(static_cast<uint32_t>(0)); |
| SetRegisterValues<int, RV32Register>({{kRmName, rm}}); |
| SetRegisterValues<R, DestRegisterType>({{kRdName, 0}}); |
| |
| inst->Execute(nullptr); |
| // Get the fflags for the instruction execution. |
| auto instruction_fflags = rv_fp_->fflags()->GetUint32(); |
| rv_fp_->fflags()->Write(static_cast<uint32_t>(0)); |
| R op_val; |
| { |
| ScopedFPStatus set_fpstatus(rv_fp_->host_fp_interface()); |
| op_val = operation(lhs_span[i], mhs_span[i], rhs_span[i]); |
| } |
| auto reg_val = state_->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]), " ", |
| FloatingPointToString<MHS>(mhs_span[i]), " ", |
| FloatingPointToString<RHS>(rhs_span[i]))); |
| |
| auto test_operation_fflags = rv_fp_->fflags()->GetUint32(); |
| EXPECT_EQ(test_operation_fflags, instruction_fflags) << absl::StrCat( |
| name, " ", i, ": ", FloatingPointToString<LHS>(lhs_span[i]), " ", |
| FloatingPointToString<MHS>(mhs_span[i]), " ", |
| FloatingPointToString<RHS>(rhs_span[i])); |
| } |
| } |
| } |
| |
| absl::Span<RV32Register *> xreg() { |
| return absl::Span<RV32Register *>(xreg_); |
| } |
| |
| absl::Span<RVFpRegister *> freg() { |
| return absl::Span<RVFpRegister *>(freg_); |
| } |
| |
| absl::Span<RV64Register *> dreg() { |
| return absl::Span<RV64Register *>(dreg_); |
| } |
| absl::BitGen &bitgen() { return bitgen_; } |
| Instruction *instruction() { return instruction_; } |
| |
| template <typename From, typename To> |
| To RoundToInteger(From val, uint32_t rm, uint32_t &flags) { |
| constexpr To kMax = std::numeric_limits<To>::max(); |
| constexpr To kMin = std::numeric_limits<To>::min(); |
| To value = 0; |
| if (FPTypeInfo<From>::IsNaN(val)) { |
| value = std::numeric_limits<To>::max(); |
| flags = (uint32_t)FPExceptions::kInvalidOp; |
| } else if (val == 0.0) { |
| value = 0; |
| } else { |
| using FromUint = typename FPTypeInfo<From>::IntType; |
| auto constexpr kBias = FPTypeInfo<From>::kExpBias; |
| auto constexpr kExpMask = FPTypeInfo<From>::kExpMask; |
| auto constexpr kSigSize = FPTypeInfo<From>::kSigSize; |
| auto constexpr kSigMask = FPTypeInfo<From>::kSigMask; |
| auto constexpr kBitSize = FPTypeInfo<From>::kBitSize; |
| FromUint val_u = absl::bit_cast<FromUint>(val); |
| FromUint exp = kExpMask & val_u; |
| const bool sign = (val_u & (1ULL << (kBitSize - 1))) != 0; |
| int exp_value = exp >> kSigSize; |
| int unbiased_exp = exp_value - kBias; |
| FromUint sig = kSigMask & val_u; |
| // Turn the value into a denormal. |
| |
| int right_shift = exp ? kSigSize - 1 - unbiased_exp : 1; |
| uint64_t base = sig; |
| bool rightshift_compressed = 0; |
| if (exp == 0) { |
| // Denormalized value. |
| // Format: (-1)^sign * 2 ^(exp - bias + 1) * 0.{sig} |
| // fraction part is too small keep it as 1 bits if not zero. |
| rightshift_compressed = base != 0; |
| base = 0; |
| flags = (uint32_t)FPExceptions::kInexact; |
| } else { |
| // Normalized value. |
| // Format: (-1)^sign * 2 ^(exp - bias) * 1.{sig |
| // base = 1.{sig} (total (1 + `kSigSize`) bits) |
| base |= 1ULL << kSigSize; |
| |
| // Right shift all base part out. |
| if (right_shift > (kBitSize - 1)) { |
| rightshift_compressed = base != 0; |
| flags = (uint32_t)FPExceptions::kInexact; |
| base = 0; |
| } else if (right_shift > 0) { |
| // Right shift to leave only 1 bit in the fraction part, compressed |
| // the right-shift eliminated number. |
| right_shift = std::min(right_shift, kBitSize); |
| uint64_t right_shifted_sig_mask = (1ULL << right_shift) - 1; |
| rightshift_compressed = (base & right_shifted_sig_mask) != 0; |
| base >>= right_shift; |
| } |
| } |
| |
| // Handle fraction part rounding. |
| if (right_shift >= 0) { |
| switch ((FPRoundingMode)rm) { |
| case FPRoundingMode::kRoundToNearest: |
| // 0.5, tie condition |
| if (rightshift_compressed == 0 && base & 0b1) { |
| // <odd>.5 -> <odd>.5 + 0.5 = even |
| if ((base & 0b11) == 0b11) { |
| base += 0b01; |
| flags = (uint32_t)FPExceptions::kInexact; |
| } |
| } else if (base & 0b1 || rightshift_compressed) { |
| // not tie condition, round to nearest integer, it equals to add |
| // 0.5(=base + 0b01) and eliminate the fraction part. |
| base += 0b01; |
| } |
| break; |
| case FPRoundingMode::kRoundTowardsZero: |
| // Round towards zero will eliminate the fraction part. |
| // Do nothing on fraction part. |
| // 1.2 -> 1.0, -1.5 -> -1.0, -0.7 -> 0.0 |
| break; |
| case FPRoundingMode::kRoundDown: |
| // Positive float will eliminate the fraction part. |
| // Negative float with fraction part will subtract 1(= base + 0b10), |
| // and eliminate the fraction part. |
| // e.g., 1.2 -> 1.0, -1.5 -> -2.0, -0.7 -> -1.0 |
| if (sign && (base & 0b1 || rightshift_compressed)) { |
| base += 0b10; |
| } |
| break; |
| case FPRoundingMode::kRoundUp: |
| // Positive float will add 1(= base + 0b10), and eliminate the |
| // fraction part. |
| // Negative float will eliminate the fraction part. |
| // e.g., 1.2 -> 2.0, -1.5 -> -1.0, -0.7 -> 0.0 |
| if (!sign && (base & 0b1 || rightshift_compressed)) { |
| base += 0b10; |
| } |
| break; |
| case FPRoundingMode::kRoundToNearestTiesToMax: |
| // Round to nearest integer that is far from zero. |
| // e.g., 1.2 -> 2.0, -1.5 -> -2.0, -0.7 -> -1.0 |
| if (base & 0b1 || rightshift_compressed) { |
| base += 0b1; |
| } |
| break; |
| default: |
| LOG(ERROR) << "Invalid rounding mode"; |
| return To(); |
| } |
| } |
| uint64_t unsigned_value; |
| // Handle base with fraction part and store it to `unsigned_value`. |
| if (right_shift >= 0) { |
| // Set inexact flag if floating value has fraction part. |
| if (base & 0b1 || rightshift_compressed) { |
| flags = (uint32_t)FPExceptions::kInexact; |
| } |
| unsigned_value = base >> 1; |
| } else { |
| // Handle base without fraction part but need to left shift. |
| int left_shift = -right_shift - 1; |
| auto prev = unsigned_value = base; |
| while (left_shift) { |
| unsigned_value <<= 1; |
| // Check if overflow happened and set the flag. |
| if (prev > unsigned_value) { |
| flags = (uint32_t)FPExceptions::kInvalidOp; |
| unsigned_value = sign ? kMin : kMax; |
| break; |
| } |
| prev = unsigned_value; |
| --left_shift; |
| } |
| } |
| |
| // Handle the case that value is out of range, and final convert to value |
| // with sign. |
| if (std::is_signed<To>::value) { |
| // Positive value but exceeds the max value. |
| if (!sign && unsigned_value > kMax) { |
| flags = (uint32_t)FPExceptions::kInvalidOp; |
| value = kMax; |
| } else if (sign && (unsigned_value > 0 && -unsigned_value < kMin)) { |
| // Negative value but exceeds the min value. |
| flags = (uint32_t)FPExceptions::kInvalidOp; |
| value = kMin; |
| } else { |
| value = sign ? -((To)unsigned_value) : unsigned_value; |
| } |
| } else { |
| // Positive value but exceeds the max value. |
| if (unsigned_value > kMax) { |
| flags = (uint32_t)FPExceptions::kInvalidOp; |
| value = sign ? kMin : kMax; |
| } else if (sign && unsigned_value != 0) { |
| // float is negative value this is out of range of valid unsigned |
| // value. |
| flags = (uint32_t)FPExceptions::kInvalidOp; |
| value = kMin; |
| } else { |
| value = sign ? -((To)unsigned_value) : unsigned_value; |
| } |
| } |
| } |
| return value; |
| } |
| |
| protected: |
| RV32Register *xreg_[32]; |
| RV64Register *dreg_[32]; |
| RVFpRegister *freg_[32]; |
| RiscVState *state_; |
| Instruction *instruction_; |
| Instruction *child_instruction_; |
| FlatDemandMemory *memory_; |
| RiscVFPState *rv_fp_; |
| absl::BitGen bitgen_; |
| }; |
| |
| namespace internal { |
| |
| template <typename T> |
| struct UnsignedToFpType {}; |
| |
| template <> |
| struct UnsignedToFpType<uint16_t> { |
| using FpType = HalfFP; |
| }; |
| |
| template <> |
| struct UnsignedToFpType<uint32_t> { |
| using FpType = float; |
| }; |
| |
| template <> |
| struct UnsignedToFpType<uint64_t> { |
| using FpType = double; |
| }; |
| |
| template <typename T> |
| double ToDouble(T input) { |
| using IntType = typename FPTypeInfo<T>::IntType; |
| using FpType = typename internal::UnsignedToFpType<IntType>::FpType; |
| |
| IntType uint_val = absl::bit_cast<IntType>(input); |
| FpType fp_val = absl::bit_cast<FpType>(uint_val); |
| if (FPTypeInfo<FpType>::IsNaN(fp_val)) { |
| return std::numeric_limits<double>::quiet_NaN(); |
| } else if (FPTypeInfo<FpType>::IsInf(fp_val)) { |
| return std::numeric_limits<double>::infinity(); |
| } |
| IntType exp = |
| (uint_val & FPTypeInfo<FpType>::kExpMask) >> FPTypeInfo<FpType>::kSigSize; |
| IntType sig = uint_val & FPTypeInfo<FpType>::kSigMask; |
| int32_t unbiased_exponent = |
| exp ? static_cast<int32_t>(exp) - FPTypeInfo<FpType>::kExpBias |
| : 1 - static_cast<int32_t>(FPTypeInfo<FpType>::kExpBias); |
| |
| double exponent_factor = std::pow(2.0, unbiased_exponent); |
| double significand_factor = static_cast<double>(sig); |
| double precision_factor = |
| std::pow(2.0, -static_cast<int32_t>(FPTypeInfo<FpType>::kSigSize)); |
| double implicit_bit_adjustment = exp ? 1.0 : 0.0; |
| double sign_factor = |
| std::pow(-1.0, uint_val >> (FPTypeInfo<FpType>::kBitSize - 1)); |
| return ((significand_factor * precision_factor) + implicit_bit_adjustment) * |
| exponent_factor * sign_factor; |
| } |
| |
| // sign, lsb, guard, round, sticky --- rm = 0 |
| inline constexpr int kRoundToNearestTable[] = { |
| 0, /*00000*/ |
| 0, /*00001*/ |
| 0, /*00010*/ |
| 0, /*00011*/ |
| 0, /*00100*/ |
| 1, /*00101*/ |
| 1, /*00110*/ |
| 1, /*00111*/ |
| 0, /*01000*/ |
| 0, /*01001*/ |
| 0, /*01010*/ |
| 0, /*01011*/ |
| 1, /*01100*/ |
| 1, /*01101*/ |
| 1, /*01110*/ |
| 1, /*01111*/ |
| 0, /*10000*/ |
| 0, /*10001*/ |
| 0, /*10010*/ |
| 0, /*10011*/ |
| 0, /*10100*/ |
| 1, /*10101*/ |
| 1, /*10110*/ |
| 1, /*10111*/ |
| 0, /*11000*/ |
| 0, /*11001*/ |
| 0, /*11010*/ |
| 0, /*11011*/ |
| 1, /*11100*/ |
| 1, /*11101*/ |
| 1, /*11110*/ |
| 1, /*11111*/ |
| }; |
| // sign, lsb, guard, round, sticky --- rm = 1 |
| inline constexpr int kRoundTowardsZeroTable[] = { |
| 0, /*00000*/ |
| 0, /*00001*/ |
| 0, /*00010*/ |
| 0, /*00011*/ |
| 0, /*00100*/ |
| 0, /*00101*/ |
| 0, /*00110*/ |
| 0, /*00111*/ |
| 0, /*01000*/ |
| 0, /*01001*/ |
| 0, /*01010*/ |
| 0, /*01011*/ |
| 0, /*01100*/ |
| 0, /*01101*/ |
| 0, /*01110*/ |
| 0, /*01111*/ |
| 0, /*10000*/ |
| 0, /*10001*/ |
| 0, /*10010*/ |
| 0, /*10011*/ |
| 0, /*10100*/ |
| 0, /*10101*/ |
| 0, /*10110*/ |
| 0, /*10111*/ |
| 0, /*11000*/ |
| 0, /*11001*/ |
| 0, /*11010*/ |
| 0, /*11011*/ |
| 0, /*11100*/ |
| 0, /*11101*/ |
| 0, /*11110*/ |
| 0, /*11111*/ |
| }; |
| // sign, lsb, guard, round, sticky --- rm = 2 |
| inline constexpr int kRoundDownTable[] = { |
| 0, /*00000*/ |
| 0, /*00001*/ |
| 0, /*00010*/ |
| 0, /*00011*/ |
| 0, /*00100*/ |
| 0, /*00101*/ |
| 0, /*00110*/ |
| 0, /*00111*/ |
| 0, /*01000*/ |
| 0, /*01001*/ |
| 0, /*01010*/ |
| 0, /*01011*/ |
| 0, /*01100*/ |
| 0, /*01101*/ |
| 0, /*01110*/ |
| 0, /*01111*/ |
| 0, /*10000*/ |
| 1, /*10001*/ |
| 1, /*10010*/ |
| 1, /*10011*/ |
| 1, /*10100*/ |
| 1, /*10101*/ |
| 1, /*10110*/ |
| 1, /*10111*/ |
| 0, /*11000*/ |
| 1, /*11001*/ |
| 1, /*11010*/ |
| 1, /*11011*/ |
| 1, /*11100*/ |
| 1, /*11101*/ |
| 1, /*11110*/ |
| 1, /*11111*/ |
| }; |
| // sign, lsb, guard, round, sticky --- rm = 3 |
| inline constexpr int kRoundUpTable[] = { |
| 0, /*00000*/ |
| 1, /*00001*/ |
| 1, /*00010*/ |
| 1, /*00011*/ |
| 1, /*00100*/ |
| 1, /*00101*/ |
| 1, /*00110*/ |
| 1, /*00111*/ |
| 0, /*01000*/ |
| 1, /*01001*/ |
| 1, /*01010*/ |
| 1, /*01011*/ |
| 1, /*01100*/ |
| 1, /*01101*/ |
| 1, /*01110*/ |
| 1, /*01111*/ |
| 0, /*10000*/ |
| 0, /*10001*/ |
| 0, /*10010*/ |
| 0, /*10011*/ |
| 0, /*10100*/ |
| 0, /*10101*/ |
| 0, /*10110*/ |
| 0, /*10111*/ |
| 0, /*11000*/ |
| 0, /*11001*/ |
| 0, /*11010*/ |
| 0, /*11011*/ |
| 0, /*11100*/ |
| 0, /*11101*/ |
| 0, /*11110*/ |
| 0, /*11111*/ |
| }; |
| |
| } // namespace internal |
| |
| template <typename T> |
| class FpConversionsTestHelper { |
| using IntType = typename FPTypeInfo<T>::IntType; |
| using FpType = typename internal::UnsignedToFpType<IntType>::FpType; |
| |
| public: |
| // The conversion helper can be used with a float in its floating point format |
| // or with its unsigned integer representation. |
| FpConversionsTestHelper(T value) : fflags_(0) { |
| if constexpr (std::is_same_v<T, IntType>) { |
| unsigned_value_ = value; |
| fp_value_ = absl::bit_cast<FpType>(unsigned_value_); |
| } else if constexpr (std::is_same_v<T, FpType>) { |
| fp_value_ = value; |
| unsigned_value_ = absl::bit_cast<IntType>(fp_value_); |
| } |
| } |
| |
| template <typename U> |
| U Convert(FPRoundingMode rm = FPRoundingMode::kRoundToNearest); |
| |
| template <typename U> |
| U ConvertWithFlags(uint32_t &fflags, |
| FPRoundingMode rm = FPRoundingMode::kRoundToNearest) { |
| fflags_ = 0; |
| U ret = Convert<U>(rm); |
| fflags = fflags_; |
| return ret; |
| } |
| |
| protected: |
| FpType fp_value_; |
| IntType unsigned_value_; |
| uint32_t fflags_; |
| |
| bool sign() { |
| return (unsigned_value_ & (1ULL << (FPTypeInfo<FpType>::kBitSize - 1))) != |
| 0; |
| } |
| |
| template <typename IntReturnType, typename FpReturnType> |
| void NarrowingConversionMakeExponentAndSignificand(FPRoundingMode, |
| IntReturnType &, |
| IntReturnType &); |
| |
| template <typename IntReturnType, typename FpReturnType> |
| IntReturnType NarrowingConversionHandleInfinity(FPRoundingMode); |
| |
| template <typename IntReturnType, typename FpReturnType> |
| IntReturnType NarrowingConversion(FPRoundingMode); |
| |
| template <typename U> |
| U RoundingRightShift(U value, int32_t shift_amt, FPRoundingMode rm) { |
| bool guard = 0; |
| bool round = 0; |
| bool sticky = 0; |
| for (int i = 0; i < shift_amt; ++i) { |
| sticky |= round; |
| round = guard; |
| guard = value & 1; |
| value >>= 1; |
| } |
| |
| bool lsb = value & 1; |
| uint8_t key = sign() << 4 | lsb << 3 | guard << 2 | round << 1 | sticky; |
| value += GetRoundingTable(rm)[key]; |
| return value; |
| } |
| |
| const int *GetRoundingTable(FPRoundingMode rm) { |
| switch (rm) { |
| case FPRoundingMode::kRoundToNearest: |
| return static_cast<const int *>(internal::kRoundToNearestTable); |
| case FPRoundingMode::kRoundTowardsZero: |
| return static_cast<const int *>(internal::kRoundTowardsZeroTable); |
| case FPRoundingMode::kRoundDown: |
| return static_cast<const int *>(internal::kRoundDownTable); |
| case FPRoundingMode::kRoundUp: |
| return static_cast<const int *>(internal::kRoundUpTable); |
| default: |
| return static_cast<const int *>(internal::kRoundToNearestTable); |
| } |
| } |
| }; // class FpConversionsTestHelper |
| |
| template <typename T> |
| template <typename U> |
| U FpConversionsTestHelper<T>::Convert(FPRoundingMode rm) { |
| using IntReturnType = typename FPTypeInfo<U>::IntType; |
| using FpReturnType = |
| typename internal::UnsignedToFpType<IntReturnType>::FpType; |
| |
| if constexpr (std::is_same_v<U, IntType>) { |
| return unsigned_value_; |
| } else if constexpr (std::is_same_v<U, FpType>) { |
| return fp_value_; |
| } |
| |
| if (FPTypeInfo<FpType>::IsNaN(fp_value_) && |
| !FPTypeInfo<FpType>::IsQNaN(fp_value_)) { |
| fflags_ |= static_cast<uint32_t>(FPExceptions::kInvalidOp); |
| } |
| |
| if (FPTypeInfo<FpType>::IsNaN(fp_value_)) { |
| return absl::bit_cast<U>(FPTypeInfo<FpReturnType>::kCanonicalNaN); |
| } else if (FPTypeInfo<FpType>::kPosInf == unsigned_value_) { |
| return absl::bit_cast<U>(FPTypeInfo<FpReturnType>::kPosInf); |
| } else if (FPTypeInfo<FpType>::kNegInf == unsigned_value_) { |
| return absl::bit_cast<U>(FPTypeInfo<FpReturnType>::kNegInf); |
| } else if (FPTypeInfo<FpType>::kPosZero == unsigned_value_) { |
| return absl::bit_cast<U>(FPTypeInfo<FpReturnType>::kPosZero); |
| } else if (FPTypeInfo<FpType>::kNegZero == unsigned_value_) { |
| return absl::bit_cast<U>(FPTypeInfo<FpReturnType>::kNegZero); |
| } |
| |
| if constexpr (std::numeric_limits<IntReturnType>::digits > |
| std::numeric_limits<IntType>::digits) { |
| // The return type is larger so the conversion is simple. |
| FpReturnType mantissa = static_cast<FpReturnType>( |
| unsigned_value_ & FPTypeInfo<FpType>::kSigMask); |
| FpReturnType precision_factor = |
| std::pow(2.0, -static_cast<FpReturnType>(FPTypeInfo<FpType>::kSigSize)); |
| IntType biased_exponent = |
| (unsigned_value_ & FPTypeInfo<FpType>::kExpMask) >> |
| FPTypeInfo<FpType>::kSigSize; |
| int32_t unbiased_exponent = |
| (biased_exponent ? static_cast<int32_t>(biased_exponent) : 1) - |
| FPTypeInfo<FpType>::kExpBias; |
| |
| // Use the formula from the IEEE754 section 3.4 that details moving from |
| // the binary format to the number being represented. |
| FpReturnType implicit_bit_adjustment = biased_exponent ? 1.0 : 0.0; |
| FpReturnType exponent_factor = std::pow(2.0, unbiased_exponent); |
| FpReturnType unsigned_result = |
| ((mantissa * precision_factor) + implicit_bit_adjustment) * |
| exponent_factor; |
| IntReturnType result = absl::bit_cast<IntReturnType>(unsigned_result) | |
| (static_cast<IntReturnType>(sign()) |
| << (FPTypeInfo<FpReturnType>::kBitSize - 1)); |
| return absl::bit_cast<U>(result); |
| } |
| |
| // If the return type is smaller then call through to the narrowing |
| // conversion. |
| return absl::bit_cast<U>( |
| NarrowingConversion<IntReturnType, FpReturnType>(rm)); |
| } |
| |
| template <typename T> |
| template <typename IntReturnType, typename FpReturnType> |
| void FpConversionsTestHelper<T>::NarrowingConversionMakeExponentAndSignificand( |
| FPRoundingMode rm, IntReturnType &out_exponent, |
| IntReturnType &out_significand) { |
| int32_t e_max = FPTypeInfo<FpReturnType>::kExpBias; |
| int32_t e_min = 1 - e_max; |
| IntType in_exponent = (unsigned_value_ & FPTypeInfo<FpType>::kExpMask) >> |
| FPTypeInfo<FpType>::kSigSize; |
| IntType in_significand = unsigned_value_ & FPTypeInfo<FpType>::kSigMask; |
| // Add the implicit bit to the significand. |
| if (in_exponent) { |
| in_significand |= 1ULL << FPTypeInfo<FpType>::kSigSize; |
| } |
| int32_t exponent_bias_diff = |
| FPTypeInfo<FpType>::kExpBias - FPTypeInfo<FpReturnType>::kExpBias; |
| int32_t unbiased_exponent = |
| static_cast<int32_t>(in_exponent) - FPTypeInfo<FpType>::kExpBias; |
| int32_t significand_size_diff = |
| FPTypeInfo<FpType>::kSigSize - FPTypeInfo<FpReturnType>::kSigSize; |
| |
| if (unbiased_exponent < e_min) { |
| // The destination float will be subnormal. |
| out_exponent = 0; |
| int shift_amt = significand_size_diff + (e_min - unbiased_exponent); |
| out_significand = RoundingRightShift(in_significand, shift_amt, rm); |
| } else if (unbiased_exponent > e_max) { |
| // The destination float will be infinity. |
| out_exponent = FPTypeInfo<FpReturnType>::kExpMask >> |
| FPTypeInfo<FpReturnType>::kSigSize; |
| out_significand = 0; |
| } else { |
| // The destination float will be normal. |
| out_exponent = in_exponent - exponent_bias_diff; |
| out_significand = |
| RoundingRightShift(in_significand & FPTypeInfo<FpType>::kSigMask, |
| significand_size_diff, rm); |
| } |
| // Rounding can cause the significand to overflow. Remask and increment the |
| // exponent to fix. |
| if ((out_significand & FPTypeInfo<FpReturnType>::kSigMask) != |
| out_significand) { |
| out_exponent = |
| std::min(out_exponent + 1, FPTypeInfo<FpReturnType>::kExpMask >> |
| FPTypeInfo<FpReturnType>::kSigSize); |
| out_significand &= FPTypeInfo<FpReturnType>::kSigMask; |
| } |
| } |
| |
| template <typename T> |
| template <typename IntReturnType, typename FpReturnType> |
| IntReturnType FpConversionsTestHelper<T>::NarrowingConversionHandleInfinity( |
| FPRoundingMode rm) { |
| IntReturnType out_uint = sign() ? FPTypeInfo<FpReturnType>::kNegInf |
| : FPTypeInfo<FpReturnType>::kPosInf; |
| int32_t e_max = FPTypeInfo<FpReturnType>::kExpBias; |
| double fp_value_double = internal::ToDouble(fp_value_); |
| double largest_non_inf_double = internal::ToDouble<IntReturnType>( |
| (sign() ? FPTypeInfo<FpReturnType>::kNegInf - 1 |
| : FPTypeInfo<FpReturnType>::kPosInf - 1)); |
| // To handle the cases near infinity, we need to consider what the |
| // conversion would have been if the exponent was unbounded. |
| double first_out_of_range_double = |
| std::pow(2.0, e_max + 1) * std::pow(-1.0, sign()); |
| |
| // Figure out if the input is closer to the largest non-inf or the unbounded |
| // number. |
| double distance_to_largest_non_inf = |
| std::abs(fp_value_double - largest_non_inf_double); |
| double distance_to_first_out_of_range = |
| std::abs(fp_value_double - first_out_of_range_double); |
| switch (rm) { |
| case FPRoundingMode::kRoundToNearest: |
| if (distance_to_largest_non_inf < distance_to_first_out_of_range) { |
| out_uint = sign() ? FPTypeInfo<FpReturnType>::kNegInf - 1 |
| : FPTypeInfo<FpReturnType>::kPosInf - 1; |
| } |
| break; |
| case FPRoundingMode::kRoundTowardsZero: |
| out_uint = sign() ? FPTypeInfo<FpReturnType>::kNegInf - 1 |
| : FPTypeInfo<FpReturnType>::kPosInf - 1; |
| break; |
| case FPRoundingMode::kRoundDown: |
| out_uint = sign() ? FPTypeInfo<FpReturnType>::kNegInf |
| : FPTypeInfo<FpReturnType>::kPosInf - 1; |
| break; |
| case FPRoundingMode::kRoundUp: |
| out_uint = sign() ? FPTypeInfo<FpReturnType>::kNegInf - 1 |
| : FPTypeInfo<FpReturnType>::kPosInf; |
| break; |
| default: |
| break; |
| } |
| fflags_ |= static_cast<uint32_t>(FPExceptions::kOverflow); |
| fflags_ |= static_cast<uint32_t>(FPExceptions::kInexact); |
| return out_uint; |
| } |
| |
| template <typename T> |
| template <typename IntReturnType, typename FpReturnType> |
| IntReturnType FpConversionsTestHelper<T>::NarrowingConversion( |
| FPRoundingMode rm) { |
| int32_t e_min = 1 - FPTypeInfo<FpReturnType>::kExpBias; |
| IntReturnType out_exponent = 0; |
| IntReturnType out_significand = 0; |
| NarrowingConversionMakeExponentAndSignificand<IntReturnType, FpReturnType>( |
| rm, out_exponent, out_significand); |
| |
| IntReturnType out_uint = sign() ? FPTypeInfo<FpReturnType>::kNegZero |
| : FPTypeInfo<FpReturnType>::kPosZero; |
| out_uint |= out_significand & FPTypeInfo<FpReturnType>::kSigMask; |
| out_uint |= (out_exponent << FPTypeInfo<FpReturnType>::kSigSize) & |
| FPTypeInfo<FpReturnType>::kExpMask; |
| |
| if (out_uint == FPTypeInfo<FpReturnType>::kPosInf || |
| out_uint == FPTypeInfo<FpReturnType>::kNegInf) { |
| // Handle rounding and flags for infinity. |
| out_uint = |
| NarrowingConversionHandleInfinity<IntReturnType, FpReturnType>(rm); |
| } else { |
| // Handle the flags not related to infinity. |
| double fp_value_double = internal::ToDouble<FpType>(fp_value_); |
| double result = internal::ToDouble<IntReturnType>(out_uint); |
| |
| if (result != fp_value_double) { |
| double b_emin = std::pow(2.0, e_min); |
| if (std::abs(result) <= b_emin || std::abs(fp_value_double) <= b_emin) { |
| fflags_ |= static_cast<uint32_t>(FPExceptions::kUnderflow); |
| } |
| fflags_ |= static_cast<uint32_t>(FPExceptions::kInexact); |
| } |
| } |
| return out_uint; |
| } |
| |
| template <typename T> |
| FpConversionsTestHelper(T) -> FpConversionsTestHelper<T>; |
| |
| } // namespace test |
| } // namespace riscv |
| } // namespace sim |
| } // namespace mpact |
| |
| #endif // MPACT_RISCV_RISCV_TEST_RISCV_FP_TEST_BASE_H_ |