| /* |
| * Copyright 2024 Google LLC |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://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_CHERIOT___TEST_RISCV_CHERIOT_FP_TEST_BASE_H_ |
| #define MPACT_CHERIOT___TEST_RISCV_CHERIOT_FP_TEST_BASE_H_ |
| |
| #include <cmath> |
| #include <cstdint> |
| #include <functional> |
| #include <ios> |
| #include <limits> |
| #include <string> |
| #include <tuple> |
| #include <type_traits> |
| #include <vector> |
| |
| #include "absl/random/random.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/string_view.h" |
| #include "absl/types/span.h" |
| #include "cheriot/cheriot_register.h" |
| #include "cheriot/cheriot_state.h" |
| #include "cheriot/riscv_cheriot_fp_state.h" |
| #include "googlemock/include/gmock/gmock.h" |
| #include "mpact/sim/generic/instruction.h" |
| #include "mpact/sim/util/memory/tagged_flat_demand_memory.h" |
| #include "riscv//riscv_fp_host.h" |
| #include "riscv//riscv_fp_info.h" |
| #include "riscv//riscv_register.h" |
| |
| namespace mpact { |
| namespace sim { |
| namespace cheriot { |
| namespace test { |
| |
| using ::mpact::sim::cheriot::CheriotRegister; |
| using ::mpact::sim::cheriot::CheriotState; |
| using ::mpact::sim::cheriot::RiscVCheriotFPState; |
| using ::mpact::sim::generic::Instruction; |
| using ::mpact::sim::riscv::FPRoundingMode; |
| using ::mpact::sim::riscv::RV64Register; |
| using ::mpact::sim::riscv::RVFpRegister; |
| using ::mpact::sim::riscv::ScopedFPRoundingMode; |
| using ::mpact::sim::riscv::ScopedFPStatus; |
| using ::mpact::sim::util::TaggedFlatDemandMemory; |
| |
| 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); } |
| }; |
| |
| 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); } |
| }; |
| |
| // 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 = |
| *reinterpret_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 = *reinterpret_cast<UInt *>(&op); |
| UInt u_reg = *reinterpret_cast<UInt *>(®); |
| 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 = *reinterpret_cast<T *>(&udelta); |
| } else { |
| // Becomes a denormal |
| int diff = delta_position - exp; |
| UInt udelta = 1ULL << (FPTypeInfo<T>::kSigSize - 1 - diff); |
| delta = *reinterpret_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 = *reinterpret_cast<UInt *>(&op); |
| UInt u_reg = *reinterpret_cast<UInt *>(®); |
| 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 = *reinterpret_cast<T *>(&udelta); |
| } else { |
| // Becomes a denormal |
| int diff = delta_position - exp; |
| UInt udelta = 1ULL << (FPTypeInfo<T>::kSigSize - 1 - diff); |
| delta = *reinterpret_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 <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) && |
| std::is_floating_point<S>::value && |
| std::is_integral<D>::value; |
| }; |
| |
| template <typename S, typename D> |
| struct GreaterSize { |
| static const bool value = |
| sizeof(S) > sizeof(D) && |
| std::is_floating_point<S>::value &&std::is_integral<D>::value; |
| }; |
| |
| template <typename S, typename D> |
| struct LessSize { |
| static const bool value = sizeof(S) < sizeof(D) && |
| std::is_floating_point<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 = *reinterpret_cast<SInt *>(&value); |
| D dval = (~static_cast<D>(0) << (sizeof(S) * 8)) | sval; |
| return *reinterpret_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 *reinterpret_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 TaggedFlatDemandMemory(8); |
| state_ = new CheriotState("test", memory_); |
| rv_fp_ = new RiscVCheriotFPState(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++) { |
| creg_[i] = |
| state_->GetRegister<CheriotRegister>(absl::StrCat("c", 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_; |
| rv_fp_ = nullptr; |
| state_->set_rv_fp(nullptr); |
| delete state_; |
| state_ = nullptr; |
| if (instruction_ != nullptr) instruction_->DecRef(); |
| instruction_ = nullptr; |
| if (child_instruction_ != nullptr) child_instruction_->DecRef(); |
| child_instruction_ = nullptr; |
| delete memory_; |
| memory_ = nullptr; |
| } |
| |
| // 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. |
| void AppendRegisterOperands(Instruction *inst, |
| const std::vector<std::string> &sources, |
| const std::vector<std::string> &destinations) { |
| for (auto ®_name : sources) { |
| auto *reg = state_->GetRegister<CheriotRegister>(reg_name).first; |
| inst->AppendSource(reg->CreateSourceOperand()); |
| } |
| for (auto ®_name : destinations) { |
| auto *reg = state_->GetRegister<CheriotRegister>(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. |
| void AppendRegisterOperands(const std::vector<std::string> &sources, |
| const std::vector<std::string> &destinations) { |
| AppendRegisterOperands(instruction_, sources, destinations); |
| } |
| |
| // named register and sets it to the corresponding value. |
| template <typename T, typename RegisterType = CheriotRegister> |
| 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(); |
| } |
| } |
| |
| // 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; |
| T val = *reinterpret_cast<T *>(&value); |
| return val; |
| } |
| |
| // 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); |
| AppendRegisterOperands({kR1Name, kRmName}, {kRdName}); |
| 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++) { |
| SetRegisterValues<LHS, LhsRegisterType>({{kR1Name, lhs_span[i]}}); |
| |
| for (int rm : {0, 1, 2, 3, 4}) { |
| rv_fp_->SetRoundingMode(static_cast<FPRoundingMode>(rm)); |
| SetRegisterValues<int, CheriotRegister>({{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]); |
| } |
| 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, ": ", 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)> 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); |
| AppendRegisterOperands({kR1Name, kRmName}, {kRdName}); |
| 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++) { |
| SetRegisterValues<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, CheriotRegister>({{kRmName, rm}, {}}); |
| SetRegisterValues<R, DestRegisterType>({{kRdName, 0}}); |
| |
| inst->Execute(nullptr); |
| auto fflags = rv_fp_->fflags()->GetUint32(); |
| |
| R op_val; |
| uint32_t flag; |
| { |
| ScopedFPRoundingMode scoped_rm(rv_fp_->host_fp_interface(), rm); |
| std::tie(op_val, flag) = 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, ": ", lhs_span[i], " rm: ", rm)); |
| auto lhs_uint = *reinterpret_cast<LhsInt *>(&lhs_span[i]); |
| auto op_val_uint = *reinterpret_cast<RInt *>(&op_val); |
| EXPECT_EQ(flag, fflags) |
| << name << "(" << lhs_span[i] << ") " << std::hex << name << "(0x" |
| << lhs_uint << ") == " << 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); |
| AppendRegisterOperands({kR1Name, kR2Name, kRmName}, {kRdName}); |
| 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++) { |
| SetRegisterValues<LHS, LhsRegisterType>({{kR1Name, lhs_span[i]}}); |
| SetRegisterValues<RHS, RhsRegisterType>({{kR2Name, rhs_span[i]}}); |
| |
| for (int rm : {0, 1, 2, 3, 4}) { |
| rv_fp_->SetRoundingMode(static_cast<FPRoundingMode>(rm)); |
| SetRegisterValues<int, CheriotRegister>({{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, ": ", lhs_span[i], " ", |
| 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); |
| AppendRegisterOperands({kR1Name, kR2Name, kRmName}, {kRdName}); |
| 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++) { |
| SetRegisterValues<LHS, LhsRegisterType>({{kR1Name, lhs_span[i]}}); |
| SetRegisterValues<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, CheriotRegister>({{kRmName, rm}, {}}); |
| SetRegisterValues<R, DestRegisterType>({{kRdName, 0}}); |
| |
| inst->Execute(nullptr); |
| |
| R op_val; |
| uint32_t flag; |
| { |
| ScopedFPStatus set_fpstatus(rv_fp_->host_fp_interface()); |
| std::tie(op_val, flag) = 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, ": ", lhs_span[i], " ", rhs_span[i])); |
| auto lhs_uint = *reinterpret_cast<LhsUInt *>(&lhs_span[i]); |
| auto rhs_uint = *reinterpret_cast<RhsUInt *>(&rhs_span[i]); |
| auto fflags = rv_fp_->fflags()->GetUint32(); |
| EXPECT_EQ(flag, 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); |
| AppendRegisterOperands({kR1Name, kR2Name, kR3Name, kRmName}, {kRdName}); |
| 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++) { |
| SetRegisterValues<LHS, LhsRegisterType>({{kR1Name, lhs_span[i]}}); |
| SetRegisterValues<MHS, MhsRegisterType>({{kR2Name, mhs_span[i]}}); |
| SetRegisterValues<RHS, RhsRegisterType>({{kR3Name, rhs_span[i]}}); |
| |
| for (int rm : {0, 1, 2, 3, 4}) { |
| rv_fp_->SetRoundingMode(static_cast<FPRoundingMode>(rm)); |
| SetRegisterValues<int, CheriotRegister>({{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, ": ", lhs_span[i], " ", rhs_span[i])); |
| } |
| } |
| } |
| |
| absl::Span<CheriotRegister *> creg() { |
| return absl::Span<CheriotRegister *>(creg_); |
| } |
| |
| 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 T> |
| T RoundToInteger(T val) { |
| using FromUint = typename FPTypeInfo<T>::IntType; |
| auto constexpr kBias = FPTypeInfo<T>::kExpBias; |
| auto constexpr kExpMask = FPTypeInfo<T>::kExpMask; |
| auto constexpr kSigSize = FPTypeInfo<T>::kSigSize; |
| auto constexpr kSigMask = FPTypeInfo<T>::kSigMask; |
| FromUint val_u = *reinterpret_cast<FromUint *>(&val); |
| FromUint exp = kExpMask & val_u; |
| FromUint sign = val_u & (1ULL << (FPTypeInfo<T>::kBitSize - 1)); |
| int exp_value = exp >> kSigSize; |
| FromUint sig = kSigMask & val_u; |
| // Turn the value into a denormal. |
| constexpr FromUint hidden = 1ULL << (kSigSize - 1); |
| FromUint tmp_u = sign | ((exp != 0) ? hidden : 0ULL) | (sig >> 1); |
| T tmp = *reinterpret_cast<T *>(&tmp_u); |
| if ((exp_value >= kBias) && (exp_value - kBias + 1 < kSigSize)) { |
| // Divide so that only the bits we care about are left in the significand. |
| int shift = kBias + kSigSize - exp_value - 1; |
| FromUint div_exp = shift + kBias; |
| FromUint div_u = div_exp << kSigSize; |
| T div = *reinterpret_cast<T *>(&div_u); |
| tmp /= div; |
| // Convert back to normalized number, by using the original sign |
| // and exponent, and the normalized and significand from the division. |
| tmp_u = *reinterpret_cast<FromUint *>(&tmp); |
| val_u = sign | exp | ((tmp_u << (shift + 1)) & kSigMask); |
| val = *reinterpret_cast<T *>(&val_u); |
| } |
| return val; |
| } |
| |
| protected: |
| CheriotRegister *creg_[32]; |
| RV64Register *dreg_[32]; |
| RVFpRegister *freg_[32]; |
| CheriotState *state_; |
| Instruction *instruction_; |
| Instruction *child_instruction_; |
| TaggedFlatDemandMemory *memory_; |
| RiscVCheriotFPState *rv_fp_; |
| absl::BitGen bitgen_; |
| }; |
| |
| } // namespace test |
| } // namespace cheriot |
| } // namespace sim |
| } // namespace mpact |
| |
| #endif // MPACT_CHERIOT___TEST_RISCV_CHERIOT_FP_TEST_BASE_H_ |