blob: b47923d6ee6d565baec69812a0b59b1330c56c54 [file] [log] [blame]
/*
* 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 *>(&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 <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 &reg_name : sources) {
auto *reg = state_->GetRegister<CheriotRegister>(reg_name).first;
inst->AppendSource(reg->CreateSourceOperand());
}
for (auto &reg_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_