blob: 81c8b0721e7b1d4bc0d2656ffa6babdc04940f25 [file]
// 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 <cmath>
#include <cstdint>
#include <functional>
#include <ios>
#include <limits>
#include <string>
#include <tuple>
#include <type_traits>
#include <vector>
#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::IsMpactFp;
using ::mpact::sim::generic::Instruction;
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 = *reinterpret_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 = *reinterpret_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 = *reinterpret_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 =
*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 *>(&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 = *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 *>(&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 = *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 <>
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 *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 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 &reg_name : sources) {
auto *reg = state_->GetRegister<T>(reg_name).first;
inst->AppendSource(reg->CreateSourceOperand());
}
for (auto &reg_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;
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);
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<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, ": ",
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<R, 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));
auto lhs_uint = *reinterpret_cast<LhsInt *>(&lhs_span[i]);
auto op_val_uint = *reinterpret_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])));
auto lhs_uint = *reinterpret_cast<LhsUInt *>(&lhs_span[i]);
auto rhs_uint = *reinterpret_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 = *reinterpret_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 test
} // namespace riscv
} // namespace sim
} // namespace mpact
#endif // MPACT_RISCV_RISCV_TEST_RISCV_FP_TEST_BASE_H_