blob: 4fb1e27ce9f85919d38767fa9650f6526a25e550 [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.
#include "cheriot/riscv_cheriot_f_instructions.h"
#include <cmath>
#include <cstdint>
#include <tuple>
#include "cheriot/test/riscv_cheriot_fp_test_base.h"
#include "googlemock/include/gmock/gmock.h"
#include "mpact/sim/generic/instruction.h"
#include "riscv//riscv_register.h"
namespace {
using ::mpact::sim::cheriot::test::FPTypeInfo;
using ::mpact::sim::cheriot::test::OptimizationBarrier;
using ::mpact::sim::cheriot::test::RiscVFPInstructionTestBase;
using ::mpact::sim::riscv::FPExceptions;
using ::mpact::sim::riscv::RVFpRegister;
using ::mpact::sim::cheriot::RiscVFAdd;
using ::mpact::sim::cheriot::RiscVFClass;
using ::mpact::sim::cheriot::RiscVFCmpeq;
using ::mpact::sim::cheriot::RiscVFCmple;
using ::mpact::sim::cheriot::RiscVFCmplt;
using ::mpact::sim::cheriot::RiscVFCvtSw;
using ::mpact::sim::cheriot::RiscVFCvtSwu;
using ::mpact::sim::cheriot::RiscVFCvtWs;
using ::mpact::sim::cheriot::RiscVFCvtWus;
using ::mpact::sim::cheriot::RiscVFDiv;
using ::mpact::sim::cheriot::RiscVFMadd;
using ::mpact::sim::cheriot::RiscVFMax;
using ::mpact::sim::cheriot::RiscVFMin;
using ::mpact::sim::cheriot::RiscVFMsub;
using ::mpact::sim::cheriot::RiscVFMul;
using ::mpact::sim::cheriot::RiscVFNmadd;
using ::mpact::sim::cheriot::RiscVFNmsub;
using ::mpact::sim::cheriot::RiscVFSgnj;
using ::mpact::sim::cheriot::RiscVFSgnjn;
using ::mpact::sim::cheriot::RiscVFSgnjx;
using ::mpact::sim::cheriot::RiscVFSqrt;
using ::mpact::sim::cheriot::RiscVFSub;
class RVCheriot32FInstructionTest : public RiscVFPInstructionTestBase {};
static bool is_snan(float a) {
if (!std::isnan(a)) return false;
uint32_t ua = *reinterpret_cast<uint32_t *>(&a);
if ((ua & (1 << (FPTypeInfo<float>::kSigSize - 1))) == 0) return true;
return false;
}
// Test basic arithmetic instructions.
TEST_F(RVCheriot32FInstructionTest, RiscVFadd) {
SetSemanticFunction(&RiscVFAdd);
BinaryOpFPTestHelper<float, float, float>(
"fadd", instruction_, {"f", "f", "f"}, 32,
[](float lhs, float rhs) -> float { return lhs + rhs; });
}
TEST_F(RVCheriot32FInstructionTest, RiscVFsub) {
SetSemanticFunction(&RiscVFSub);
BinaryOpFPTestHelper<float, float, float>(
"fsub", instruction_, {"f", "f", "f"}, 32,
[](float lhs, float rhs) -> float { return lhs - rhs; });
}
TEST_F(RVCheriot32FInstructionTest, RiscVFmul) {
SetSemanticFunction(&RiscVFMul);
BinaryOpFPTestHelper<float, float, float>(
"fmul", instruction_, {"f", "f", "f"}, 32,
[](float lhs, float rhs) -> float { return lhs * rhs; });
}
TEST_F(RVCheriot32FInstructionTest, RiscVFdiv) {
SetSemanticFunction(&RiscVFDiv);
BinaryOpFPTestHelper<float, float, float>(
"fdiv", instruction_, {"f", "f", "f"}, 32,
[](float lhs, float rhs) -> float { return lhs / rhs; });
}
// Test square root.
TEST_F(RVCheriot32FInstructionTest, RiscVFsqrt) {
SetSemanticFunction(&RiscVFSqrt);
}
// Test Min/Max.
TEST_F(RVCheriot32FInstructionTest, RiscVFmin) {
SetSemanticFunction(&RiscVFMin);
BinaryOpWithFflagsFPTestHelper<float, float, float>(
"fmin", instruction_, {"f", "f", "f"}, 32,
[](float lhs, float rhs) -> std::tuple<float, uint32_t> {
uint32_t flag = 0;
if (is_snan(lhs) || is_snan(rhs)) {
flag = static_cast<uint32_t>(FPExceptions::kInvalidOp);
}
if (std::isnan(lhs) && std::isnan(rhs)) {
uint32_t val = FPTypeInfo<float>::kCanonicalNaN;
return std::tie(*reinterpret_cast<const float *>(&val), flag);
}
if (std::isnan(lhs)) return std::tie(rhs, flag);
if (std::isnan(rhs)) return std::tie(lhs, flag);
if ((lhs == 0.0) && (rhs == 0.0)) {
return std::tie(std::signbit(lhs) ? lhs : rhs, flag);
}
return std::tie(lhs > rhs ? rhs : lhs, flag);
});
}
TEST_F(RVCheriot32FInstructionTest, RiscVFmax) {
SetSemanticFunction(&RiscVFMax);
BinaryOpWithFflagsFPTestHelper<float, float, float>(
"fmax", instruction_, {"f", "f", "f"}, 32,
[](float lhs, float rhs) -> std::tuple<float, uint32_t> {
uint32_t flag = 0;
if (is_snan(lhs) || is_snan(rhs)) {
flag = static_cast<uint32_t>(FPExceptions::kInvalidOp);
}
if (std::isnan(lhs) && std::isnan(rhs)) {
uint32_t val = FPTypeInfo<float>::kCanonicalNaN;
return std::tie(*reinterpret_cast<const float *>(&val), flag);
}
if (std::isnan(lhs)) return std::tie(rhs, flag);
if (std::isnan(rhs)) return std::tie(lhs, flag);
if ((lhs == 0.0) && (rhs == 0.0)) {
return std::tie(std::signbit(lhs) ? rhs : lhs, flag);
}
return std::tie(lhs < rhs ? rhs : lhs, flag);
});
}
// Test MAC versions.
TEST_F(RVCheriot32FInstructionTest, RiscVFMadd) {
SetSemanticFunction(&RiscVFMadd);
TernaryOpFPTestHelper<float, float, float, float>(
"fmadd", instruction_, {"f", "f", "f", "f"}, 32,
[](float lhs, float mhs, float rhs) -> float {
return OptimizationBarrier(lhs * mhs) + rhs;
});
}
TEST_F(RVCheriot32FInstructionTest, RiscVFMsub) {
SetSemanticFunction(&RiscVFMsub);
TernaryOpFPTestHelper<float, float, float, float>(
"fmsub", instruction_, {"f", "f", "f", "f"}, 32,
[](float lhs, float mhs, float rhs) -> float {
return OptimizationBarrier(lhs * mhs) - rhs;
});
}
TEST_F(RVCheriot32FInstructionTest, RiscVFNmadd) {
SetSemanticFunction(&RiscVFNmadd);
TernaryOpFPTestHelper<float, float, float, float>(
"fnmadd", instruction_, {"f", "f", "f", "f"}, 32,
[](float lhs, float mhs, float rhs) -> float {
return -OptimizationBarrier(lhs * mhs) - rhs;
});
}
TEST_F(RVCheriot32FInstructionTest, RiscVFNmsub) {
SetSemanticFunction(&RiscVFNmsub);
TernaryOpFPTestHelper<float, float, float, float>(
"fnmsub", instruction_, {"f", "f", "f", "f"}, 32,
[](float lhs, float mhs, float rhs) -> float {
return -OptimizationBarrier(lhs * mhs) + rhs;
});
}
// Test conversion instructions.
// Double to signed 32 bit integer.
TEST_F(RVCheriot32FInstructionTest, RiscVFCvtWs) {
SetSemanticFunction(&RiscVFCvtWs);
UnaryOpWithFflagsFPTestHelper<int32_t, float>(
"fcvt.w.s", instruction_, {"f", "x"}, 32,
[&](float lhs) -> std::tuple<int32_t, uint32_t> {
if (std::isnan(lhs))
return std::make_tuple(
0x7fff'ffff, static_cast<uint32_t>(FPExceptions::kInvalidOp));
if (std::isinf(lhs))
return std::make_tuple(
lhs < 0 ? 0x8000'0000 : 0x7fff'ffff,
static_cast<uint32_t>(FPExceptions::kInvalidOp));
if (abs(lhs) > static_cast<float>(0x7fff'ffff))
return std::make_tuple(
lhs < 0 ? 0x8000'0000 : 0x7fff'ffff,
static_cast<uint32_t>(FPExceptions::kInvalidOp));
uint32_t flag = 0;
if (ceil(lhs) != lhs) {
flag |= static_cast<uint32_t>(FPExceptions::kInexact);
lhs = RoundToInteger(lhs);
}
return std::make_tuple(static_cast<int32_t>(lhs), flag);
});
}
// Signed 32 bit integer to float.
TEST_F(RVCheriot32FInstructionTest, RiscVFCvtSw) {
SetSemanticFunction(&RiscVFCvtSw);
UnaryOpFPTestHelper<float, int32_t>(
"fcvt.s.w", instruction_, {"x", "f"}, 32,
[](int32_t lhs) -> float { return static_cast<float>(lhs); });
}
// Double to unsigned 32 bit integer.
TEST_F(RVCheriot32FInstructionTest, RiscVFCvtWus) {
SetSemanticFunction(&RiscVFCvtWus);
UnaryOpWithFflagsFPTestHelper<uint32_t, float>(
"fcvt.wu.s", instruction_, {"f", "x"}, 32,
[&](float lhs) -> std::tuple<uint32_t, uint32_t> {
if (std::isnan(lhs))
return std::make_tuple(
0xffff'ffff, static_cast<uint32_t>(FPExceptions::kInvalidOp));
if (lhs < 0) {
if ((lhs > -1.0) && (static_cast<int32_t>(lhs) == 0.0)) {
return std::make_tuple(
0, static_cast<uint32_t>(FPExceptions::kInexact));
}
return std::make_tuple(
0, static_cast<uint32_t>(FPExceptions::kInvalidOp));
}
if (std::isinf(lhs) || lhs > static_cast<float>(0xffff'ffffUL)) {
return std::make_tuple(
0xffff'ffff, static_cast<uint32_t>(FPExceptions::kInvalidOp));
}
uint32_t flag = 0;
if (ceil(lhs) != lhs) {
flag |= static_cast<uint32_t>(FPExceptions::kInexact);
lhs = RoundToInteger(lhs);
}
return std::make_tuple(static_cast<uint32_t>(lhs), flag);
});
}
// Unsigned 32 bit integer to float.
TEST_F(RVCheriot32FInstructionTest, RiscVFCvtSwu) {
SetSemanticFunction(&RiscVFCvtSwu);
UnaryOpFPTestHelper<float, uint32_t>(
"fcvt.s.w", instruction_, {"x", "f"}, 32,
[](uint32_t lhs) -> float { return static_cast<float>(lhs); });
}
// Test sign manipulation instructions.
TEST_F(RVCheriot32FInstructionTest, RiscVFSgnj) {
SetSemanticFunction(&RiscVFSgnj);
BinaryOpFPTestHelper<float, float, float>(
"fsgnj", instruction_, {"f", "f", "f"}, 32,
[](float lhs, float rhs) -> float { return copysign(abs(lhs), rhs); });
}
TEST_F(RVCheriot32FInstructionTest, RiscVFSgnjn) {
SetSemanticFunction(&RiscVFSgnjn);
BinaryOpFPTestHelper<float, float, float>(
"fsgnjn", instruction_, {"f", "f", "f"}, 32,
[](float lhs, float rhs) -> float { return copysign(abs(lhs), -rhs); });
}
TEST_F(RVCheriot32FInstructionTest, RiscVFSgnjx) {
SetSemanticFunction(&RiscVFSgnjx);
BinaryOpFPTestHelper<float, float, float>(
"fsgnjn", instruction_, {"f", "f", "f"}, 32,
[](float lhs, float rhs) -> float {
auto lhs_u = *reinterpret_cast<uint32_t *>(&lhs);
auto rhs_u = *reinterpret_cast<uint32_t *>(&rhs);
auto res_u = (lhs_u ^ rhs_u) & 0x8000'0000;
auto res = *reinterpret_cast<float *>(&res_u);
return copysign(abs(lhs), res);
});
}
// Test compare instructions.
TEST_F(RVCheriot32FInstructionTest, RiscVFCmpeq) {
SetSemanticFunction(&RiscVFCmpeq);
BinaryOpWithFflagsFPTestHelper<uint32_t, float, float>(
"fcmpeq", instruction_, {"f", "f", "x"}, 32,
[](float lhs, float rhs) -> std::tuple<uint32_t, uint32_t> {
uint32_t flag = 0;
if (is_snan(lhs) || is_snan(rhs)) {
flag = static_cast<uint32_t>(FPExceptions::kInvalidOp);
}
return std::make_tuple(lhs == rhs, flag);
});
}
TEST_F(RVCheriot32FInstructionTest, RiscVFCmplt) {
SetSemanticFunction(&RiscVFCmplt);
BinaryOpWithFflagsFPTestHelper<uint32_t, float, float>(
"fcmplt", instruction_, {"f", "f", "x"}, 32,
[](float lhs, float rhs) -> std::tuple<uint32_t, uint32_t> {
uint32_t flag = 0;
if (std::isnan(lhs) || std::isnan(rhs)) {
flag = static_cast<uint32_t>(FPExceptions::kInvalidOp);
}
return std::make_tuple(lhs < rhs, flag);
});
}
TEST_F(RVCheriot32FInstructionTest, RiscVFCmple) {
SetSemanticFunction(&RiscVFCmple);
BinaryOpWithFflagsFPTestHelper<uint32_t, float, float>(
"fcmple", instruction_, {"f", "f", "x"}, 32,
[](float lhs, float rhs) -> std::tuple<uint32_t, uint32_t> {
uint32_t flag = 0;
if (std::isnan(lhs) || std::isnan(rhs)) {
flag = static_cast<uint32_t>(FPExceptions::kInvalidOp);
}
return std::make_tuple(lhs <= rhs, flag);
});
}
// Test class instruction.
TEST_F(RVCheriot32FInstructionTest, RiscVFClass) {
SetSemanticFunction(&RiscVFClass);
UnaryOpFPTestHelper<uint32_t, float>(
"fclass.d", instruction_, {"f", "x"}, 32, [](float lhs) -> uint32_t {
auto fp_class = std::fpclassify(lhs);
switch (fp_class) {
case FP_INFINITE:
return std::signbit(lhs) ? 1 : 1 << 7;
case FP_NAN: {
auto uint_val =
*reinterpret_cast<typename FPTypeInfo<float>::IntType *>(&lhs);
bool quiet_nan =
(uint_val >> (FPTypeInfo<float>::kSigSize - 1)) & 1;
return quiet_nan ? 1 << 9 : 1 << 8;
}
case FP_ZERO:
return std::signbit(lhs) ? 1 << 3 : 1 << 4;
case FP_SUBNORMAL:
return std::signbit(lhs) ? 1 << 2 : 1 << 5;
case FP_NORMAL:
return std::signbit(lhs) ? 1 << 1 : 1 << 6;
}
return 0;
});
}
} // namespace