blob: 1aaa6853ed5692bb821e0c24f9457840772cad60 [file]
// Copyright 2025 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.
#include "riscv/riscv_zfh_instructions.h"
#include <sys/types.h>
#include <algorithm>
#include <any>
#include <cassert>
#include <cmath>
#include <cstdint>
#include <ios>
#include <string>
#include <tuple>
#include <vector>
#include "absl/base/casts.h"
#include "googlemock/include/gmock/gmock.h"
#include "mpact/sim/generic/data_buffer.h"
#include "mpact/sim/generic/immediate_operand.h"
#include "mpact/sim/generic/instruction.h"
#include "mpact/sim/generic/operand_interface.h"
#include "mpact/sim/generic/type_helpers.h"
#include "riscv/riscv_fp_info.h"
#include "riscv/riscv_i_instructions.h"
#include "riscv/riscv_register.h"
#include "riscv/test/riscv_fp_test_base.h"
namespace {
using ::mpact::sim::generic::operator*; // NOLINT: is used below.
using ::mpact::sim::generic::DataBuffer;
using ::mpact::sim::generic::HalfFP;
using ::mpact::sim::generic::ImmediateOperand;
using ::mpact::sim::generic::Instruction;
using ::mpact::sim::riscv::FPRoundingMode;
using ::mpact::sim::riscv::RiscVZfhCvtDh;
using ::mpact::sim::riscv::RiscVZfhCvtHd;
using ::mpact::sim::riscv::RiscVZfhCvtHs;
using ::mpact::sim::riscv::RiscVZfhCvtSh;
using ::mpact::sim::riscv::RiscVZfhFlhChild;
using ::mpact::sim::riscv::RiscVZfhFMvhx;
using ::mpact::sim::riscv::RV32Register;
using ::mpact::sim::riscv::RVFpRegister;
using ::mpact::sim::riscv::RV32::RiscVILhu;
using ::mpact::sim::riscv::RV32::RiscVZfhFMvxh;
using ::mpact::sim::riscv::test::FpConversionsTestHelper;
using ::mpact::sim::riscv::test::FPTypeInfo;
using ::mpact::sim::riscv::test::RiscVFPInstructionTestBase;
const int kRoundingModeRoundToNearest =
static_cast<int>(FPRoundingMode::kRoundToNearest);
const int kRoundingModeRoundTowardsZero =
static_cast<int>(FPRoundingMode::kRoundTowardsZero);
const int kRoundingModeRoundDown = static_cast<int>(FPRoundingMode::kRoundDown);
const int kRoundingModeRoundUp = static_cast<int>(FPRoundingMode::kRoundUp);
// A source operand that is used to set the rounding mode. This is less
// confusing than using a register source operand since the rounding mode is
// part of the instruction encoding.
class TestRoundingModeSourceOperand
: public mpact::sim::generic::SourceOperandInterface {
public:
explicit TestRoundingModeSourceOperand()
: rounding_mode_(FPRoundingMode::kRoundToNearest) {}
void SetRoundingMode(FPRoundingMode rounding_mode) {
rounding_mode_ = rounding_mode;
}
bool AsBool(int) override { return static_cast<bool>(rounding_mode_); }
int8_t AsInt8(int) override { return static_cast<int8_t>(rounding_mode_); }
uint8_t AsUint8(int) override { return static_cast<uint8_t>(rounding_mode_); }
int16_t AsInt16(int) override { return static_cast<int16_t>(rounding_mode_); }
uint16_t AsUint16(int) override {
return static_cast<uint16_t>(rounding_mode_);
}
int32_t AsInt32(int) override { return static_cast<int32_t>(rounding_mode_); }
uint32_t AsUint32(int) override {
return static_cast<uint32_t>(rounding_mode_);
}
int64_t AsInt64(int) override { return static_cast<int64_t>(rounding_mode_); }
uint64_t AsUint64(int) override {
return static_cast<uint64_t>(rounding_mode_);
}
std::vector<int> shape() const override { return {1}; }
std::string AsString() const override { return std::string(""); }
std::any GetObject() const override { return std::any(); }
protected:
FPRoundingMode rounding_mode_;
};
class RV32ZfhInstructionTest : public RiscVFPInstructionTestBase {
protected:
// Test conversion instructions. The instance variable semantic_function_ is
// used to set the semantic function for the instruction and should be set
// before calling this function.
template <typename T, typename U, int rm = 0>
T ConversionHelper(U input_val) {
// Initialize a fresh instruction.
ResetInstruction();
assert(semantic_function_);
SetSemanticFunction(semantic_function_);
// Configure source and destination operands for the instruction.
AppendRegisterOperands<RVFpRegister>({"f1"}, {"f5"});
instruction_->AppendSource(new TestRoundingModeSourceOperand());
auto *flag_op = rv_fp_->fflags()->CreateSetDestinationOperand(0, "fflags");
instruction_->AppendDestination(flag_op);
assert(instruction_->SourcesSize() == 2);
assert(instruction_->DestinationsSize() == 2);
// Set all operands to known values before executing the instruction.
static_cast<TestRoundingModeSourceOperand *>(instruction_->Source(1))
->SetRoundingMode(static_cast<FPRoundingMode>(rm));
rv_fp_->SetRoundingMode(static_cast<FPRoundingMode>(rm));
SetNaNBoxedRegisterValues<U, RVFpRegister>({{"f1", input_val}});
SetRegisterValues<int, RVFpRegister>({{"f5", 0xDEAFBEEFDEADBEEF}});
rv_fp_->fflags()->Write(static_cast<uint32_t>(0));
instruction_->Execute(nullptr);
T reg_val = state_->GetRegister<RVFpRegister>("f5")
.first->data_buffer()
->template Get<T>(0);
return reg_val;
}
template <FPRoundingMode>
void RoundingConversionTestHelper(uint32_t, uint16_t, uint32_t &, uint32_t,
uint16_t, uint32_t &);
template <FPRoundingMode rm>
void RoundingPointTest(uint16_t);
template <typename T>
void SetupMemory(uint64_t, T);
template <typename T, typename IntegerRegister>
T LoadHalfHelper(uint64_t, int16_t);
Instruction::SemanticFunction semantic_function_ = nullptr;
};
template <FPRoundingMode rm>
void RV32ZfhInstructionTest::RoundingConversionTestHelper(
uint32_t float_uint_before, uint16_t half_uint_before,
uint32_t &first_expected_fflags, uint32_t float_uint_after,
uint16_t half_uint_after, uint32_t &second_expected_fflags) {
float input_val;
HalfFP expected_val;
HalfFP actual_val;
input_val = absl::bit_cast<float>(float_uint_before);
expected_val = {.value = half_uint_before};
actual_val = ConversionHelper<HalfFP, float, static_cast<int>(rm)>(input_val);
EXPECT_EQ(expected_val.value, actual_val.value)
<< "expected: " << std::hex << expected_val.value
<< ", actual: " << std::hex << actual_val.value
<< ", float_uint: " << std::hex << float_uint_before
<< ", rounding_mode: " << static_cast<int>(rm);
EXPECT_EQ(first_expected_fflags, rv_fp_->fflags()->GetUint32())
<< "while converting: " << std::hex << float_uint_before
<< " to:" << std::hex << actual_val.value
<< " with rounding mode: " << static_cast<int>(rm);
input_val = absl::bit_cast<float>(float_uint_after);
expected_val = {.value = half_uint_after};
actual_val = ConversionHelper<HalfFP, float, static_cast<int>(rm)>(input_val);
EXPECT_EQ(expected_val.value, actual_val.value)
<< "expected: " << std::hex << expected_val.value
<< ", actual: " << std::hex << actual_val.value
<< ", float_uint: " << std::hex << float_uint_after
<< ", rounding_mode: " << static_cast<int>(rm);
EXPECT_EQ(second_expected_fflags, rv_fp_->fflags()->GetUint32())
<< "while converting: " << std::hex << float_uint_after
<< " to:" << std::hex << actual_val.value
<< " with rounding mode: " << static_cast<int>(rm);
}
template <typename T>
void RV32ZfhInstructionTest::SetupMemory(uint64_t address, T value) {
DataBuffer *mem_db = state_->db_factory()->Allocate<T>(1);
mem_db->Set<T>(0, value);
state_->StoreMemory(instruction_, address, mem_db);
mem_db->DecRef();
}
template <typename T, typename IntegerRegister>
T RV32ZfhInstructionTest::LoadHalfHelper(uint64_t base, int16_t offset) {
const std::string kRs1Name("x1");
const std::string kFrdName("f5");
AppendRegisterOperands<IntegerRegister>({kRs1Name}, {});
AppendRegisterOperands<RVFpRegister>(child_instruction_, {}, {kFrdName});
ImmediateOperand<int16_t> *offset_source_operand =
new ImmediateOperand<int16_t>(offset);
instruction_->AppendSource(offset_source_operand);
SetRegisterValues<typename IntegerRegister::ValueType, IntegerRegister>(
{{kRs1Name, static_cast<IntegerRegister::ValueType>(base)}});
SetRegisterValues<uint32_t, RVFpRegister>({{kFrdName, 0}});
instruction_->Execute(nullptr);
T observed_val = state_->GetRegister<RVFpRegister>(kFrdName)
.first->data_buffer()
->template Get<T>(0);
return observed_val;
}
// Test the FP16 load instruction. The semantic functions should match the isa
// file.
TEST_F(RV32ZfhInstructionTest, RiscVFlh) {
SetSemanticFunction(&RiscVILhu);
SetChildInstruction();
SetChildSemanticFunction(&RiscVZfhFlhChild);
SetupMemory<uint16_t>(0xFF, 0xBEEF);
HalfFP observed_val =
LoadHalfHelper<HalfFP, RV32Register>(/* base */ 0x0, /* offset */ 0xFF);
EXPECT_EQ(observed_val.value, 0xBEEF);
}
// Test the FP16 load instruction. When looking at the register contents as a
// float, it should be NaN.
TEST_F(RV32ZfhInstructionTest, RiscVFlh_float_nanbox) {
SetSemanticFunction(&RiscVILhu);
SetChildInstruction();
SetChildSemanticFunction(&RiscVZfhFlhChild);
SetupMemory<uint16_t>(0xFF, 0xBEEF);
float observed_val =
LoadHalfHelper<float, RV32Register>(/* base */ 0xFF, /* offset */ 0);
EXPECT_TRUE(std::isnan(observed_val));
}
// Test the FP16 load instruction. When looking at the register contents as a
// double, it should be NaN.
TEST_F(RV32ZfhInstructionTest, RiscVFlh_double_nanbox) {
SetSemanticFunction(&RiscVILhu);
SetChildInstruction();
SetChildSemanticFunction(&RiscVZfhFlhChild);
SetupMemory<uint16_t>(0xFF, 0xBEEF);
double observed_val = LoadHalfHelper<double, RV32Register>(
/* base */ 0x0100, /* offset */ -1);
EXPECT_TRUE(std::isnan(observed_val));
}
// Move half precision from a float register to an integer register. The IEEE754
// encoding is preserved in the integer register.
TEST_F(RV32ZfhInstructionTest, RiscVZfhFMvxh) {
SetSemanticFunction(&RiscVZfhFMvxh);
UnaryOpFPTestHelper<uint32_t, HalfFP>(
"fmv.x.h", instruction_, {"f", "x"}, 32, [](HalfFP half_fp) -> uint32_t {
bool sign = 1 & (half_fp.value >> (FPTypeInfo<HalfFP>::kBitSize - 1));
// Fill the upper XLEN-16 bits with the sign bit as per the spec.
uint32_t result = sign ? 0xFFFF'0000 : 0;
result |= static_cast<uint32_t>(half_fp.value);
return result;
});
}
// Move half precision from an integer register (lower 16 bits) to a float
// register.
TEST_F(RV32ZfhInstructionTest, RiscVZfhFMvhx) {
SetSemanticFunction(&RiscVZfhFMvhx);
UnaryOpFPTestHelper<HalfFP, uint64_t>(
"fmv.h.x", instruction_, {"x", "f"}, 32, [](uint64_t scalar) -> HalfFP {
return HalfFP{.value = static_cast<uint16_t>(scalar)};
});
}
// Half precision to single precision conversion.
TEST_F(RV32ZfhInstructionTest, RiscVZfhCvtSh) {
SetSemanticFunction(&RiscVZfhCvtSh);
UnaryOpWithFflagsFPTestHelper<float, HalfFP>(
"fcvt.s.h", instruction_, {"f", "f"}, 32,
[](HalfFP half_fp, int rm) -> std::tuple<float, uint32_t> {
uint32_t fflags = 0;
float float_result =
FpConversionsTestHelper(half_fp).ConvertWithFlags<float>(
fflags, static_cast<FPRoundingMode>(rm));
return std::make_tuple(float_result, fflags);
});
}
// Single precision to half precision conversion.
TEST_F(RV32ZfhInstructionTest, RiscVZfhCvtHs) {
SetSemanticFunction(&RiscVZfhCvtHs);
UnaryOpWithFflagsFPTestHelper<HalfFP, float>(
"fcvt.h.s", instruction_, {"f", "f"}, 32,
[](float input_float, int rm) -> std::tuple<HalfFP, uint32_t> {
uint32_t fflags = 0;
HalfFP half_result = FpConversionsTestHelper(input_float)
.ConvertWithFlags<HalfFP>(
fflags, static_cast<FPRoundingMode>(rm));
return std::make_tuple(half_result, fflags);
});
}
// Find the nearest floats that convert to the given half precision values.
std::tuple<uint32_t, uint32_t> GetRoundingPoints(uint16_t first,
uint16_t second,
FPRoundingMode rm) {
uint16_t upper_uhalf = std::max(first, second);
uint32_t upper_ufloat = absl::bit_cast<uint32_t>(
FpConversionsTestHelper(upper_uhalf).Convert<float>(rm));
uint16_t lower_uhalf = std::min(first, second);
uint32_t lower_ufloat = absl::bit_cast<uint32_t>(
FpConversionsTestHelper(lower_uhalf).Convert<float>(rm));
while (upper_ufloat - lower_ufloat > 1) {
uint32_t udelta = upper_ufloat - lower_ufloat;
uint32_t mid_ufloat = lower_ufloat + (udelta >> 1);
HalfFP mid_half = FpConversionsTestHelper(mid_ufloat).Convert<HalfFP>(rm);
uint16_t mid_uhalf = mid_half.value;
if (upper_uhalf == mid_uhalf) {
upper_ufloat = mid_ufloat;
} else if (lower_uhalf == mid_uhalf) {
lower_ufloat = mid_ufloat;
}
}
if (first > second) {
return std::make_tuple(upper_ufloat, lower_ufloat);
}
return std::make_tuple(lower_ufloat, upper_ufloat);
}
template <FPRoundingMode rm>
void RV32ZfhInstructionTest::RoundingPointTest(uint16_t base_uhalf) {
uint16_t first_uhalf = base_uhalf, second_uhalf = base_uhalf + 1;
uint32_t first_ufloat, second_ufloat;
uint32_t first_expected_fflags = 0, second_expected_fflags = 0;
std::tie(first_ufloat, second_ufloat) =
GetRoundingPoints(first_uhalf, second_uhalf, rm);
// Get the expected fflags
FpConversionsTestHelper(first_ufloat)
.ConvertWithFlags<HalfFP>(first_expected_fflags, rm);
FpConversionsTestHelper(second_ufloat)
.ConvertWithFlags<HalfFP>(second_expected_fflags, rm);
RoundingConversionTestHelper<rm>(first_ufloat, first_uhalf,
first_expected_fflags, second_ufloat,
second_uhalf, second_expected_fflags);
}
// Verify that the rounding points in the semantic functions match the
// rounding points in the test helpers. Test across all rounding modes.
TEST_F(RV32ZfhInstructionTest,
RiscVZfhCvtHs_conversion_rounding_points_first_nonzero) {
semantic_function_ = &RiscVZfhCvtHs;
// The first float that converts to a non zero half precision value after
// rounding. Zero before, denormal after.
uint16_t pos_uhalf = 0b0'00000'0000000000;
RoundingPointTest<FPRoundingMode::kRoundToNearest>(pos_uhalf);
RoundingPointTest<FPRoundingMode::kRoundTowardsZero>(pos_uhalf);
RoundingPointTest<FPRoundingMode::kRoundDown>(pos_uhalf);
RoundingPointTest<FPRoundingMode::kRoundUp>(pos_uhalf);
uint16_t neg_uhalf = 0b1'00000'0000000000;
RoundingPointTest<FPRoundingMode::kRoundToNearest>(neg_uhalf);
RoundingPointTest<FPRoundingMode::kRoundTowardsZero>(neg_uhalf);
RoundingPointTest<FPRoundingMode::kRoundDown>(neg_uhalf);
RoundingPointTest<FPRoundingMode::kRoundUp>(neg_uhalf);
}
TEST_F(RV32ZfhInstructionTest,
RiscVZfhCvtHs_conversion_rounding_points_denrom_denorm) {
semantic_function_ = &RiscVZfhCvtHs;
// Rounding denormal before and denormal after
uint16_t pos_uhalf = 0b0'00000'0000000001;
RoundingPointTest<FPRoundingMode::kRoundToNearest>(pos_uhalf);
RoundingPointTest<FPRoundingMode::kRoundTowardsZero>(pos_uhalf);
RoundingPointTest<FPRoundingMode::kRoundDown>(pos_uhalf);
RoundingPointTest<FPRoundingMode::kRoundUp>(pos_uhalf);
uint16_t neg_uhalf = 0b1'00000'0000000001;
RoundingPointTest<FPRoundingMode::kRoundToNearest>(neg_uhalf);
RoundingPointTest<FPRoundingMode::kRoundTowardsZero>(neg_uhalf);
RoundingPointTest<FPRoundingMode::kRoundDown>(neg_uhalf);
RoundingPointTest<FPRoundingMode::kRoundUp>(neg_uhalf);
}
TEST_F(RV32ZfhInstructionTest,
RiscVZfhCvtHs_conversion_rounding_points_denorm_normal) {
semantic_function_ = &RiscVZfhCvtHs;
// The rounding overflows the significand and should increase the exponent.
// Denormal before, normal after.
uint16_t pos_uhalf = 0b0'00000'1111111111;
RoundingPointTest<FPRoundingMode::kRoundToNearest>(pos_uhalf);
RoundingPointTest<FPRoundingMode::kRoundTowardsZero>(pos_uhalf);
RoundingPointTest<FPRoundingMode::kRoundDown>(pos_uhalf);
RoundingPointTest<FPRoundingMode::kRoundUp>(pos_uhalf);
uint16_t neg_uhalf = 0b1'00000'1111111111;
RoundingPointTest<FPRoundingMode::kRoundToNearest>(neg_uhalf);
RoundingPointTest<FPRoundingMode::kRoundTowardsZero>(neg_uhalf);
RoundingPointTest<FPRoundingMode::kRoundDown>(neg_uhalf);
RoundingPointTest<FPRoundingMode::kRoundUp>(neg_uhalf);
}
TEST_F(RV32ZfhInstructionTest,
RiscVZfhCvtHs_conversion_rounding_points_normal_normal) {
semantic_function_ = &RiscVZfhCvtHs;
// Rounding normal before and normal after.
uint16_t pos_uhalf = 0b0'11110'1111111110;
RoundingPointTest<FPRoundingMode::kRoundToNearest>(pos_uhalf);
RoundingPointTest<FPRoundingMode::kRoundTowardsZero>(pos_uhalf);
RoundingPointTest<FPRoundingMode::kRoundDown>(pos_uhalf);
RoundingPointTest<FPRoundingMode::kRoundUp>(pos_uhalf);
uint16_t neg_uhalf = 0b1'11110'1111111110;
RoundingPointTest<FPRoundingMode::kRoundToNearest>(neg_uhalf);
RoundingPointTest<FPRoundingMode::kRoundTowardsZero>(neg_uhalf);
RoundingPointTest<FPRoundingMode::kRoundDown>(neg_uhalf);
RoundingPointTest<FPRoundingMode::kRoundUp>(neg_uhalf);
}
TEST_F(RV32ZfhInstructionTest, RiscVZfhCvtHs_conversion_rounding_points_inf) {
semantic_function_ = &RiscVZfhCvtHs;
// Rounding normal before and infinity after.
uint16_t pos_uhalf = 0b0'11110'1111111111;
RoundingPointTest<FPRoundingMode::kRoundToNearest>(pos_uhalf);
RoundingPointTest<FPRoundingMode::kRoundTowardsZero>(pos_uhalf);
RoundingPointTest<FPRoundingMode::kRoundDown>(pos_uhalf);
RoundingPointTest<FPRoundingMode::kRoundUp>(pos_uhalf);
uint16_t neg_uhalf = 0b1'11110'1111111111;
RoundingPointTest<FPRoundingMode::kRoundToNearest>(neg_uhalf);
RoundingPointTest<FPRoundingMode::kRoundTowardsZero>(neg_uhalf);
RoundingPointTest<FPRoundingMode::kRoundDown>(neg_uhalf);
RoundingPointTest<FPRoundingMode::kRoundUp>(neg_uhalf);
}
// Half precision to double precision conversion.
TEST_F(RV32ZfhInstructionTest, RiscVZfhCvtDh) {
SetSemanticFunction(&RiscVZfhCvtDh);
UnaryOpWithFflagsFPTestHelper<double, HalfFP>(
"fcvt.d.h", instruction_, {"f", "f"}, 32,
[](HalfFP half_fp, int rm) -> std::tuple<double, uint32_t> {
uint32_t fflags = 0;
double double_result =
FpConversionsTestHelper(half_fp).ConvertWithFlags<double>(
fflags, static_cast<FPRoundingMode>(rm));
return std::make_tuple(double_result, fflags);
});
}
// Double precision to half precision conversion.
TEST_F(RV32ZfhInstructionTest, RiscVZfhCvtHd) {
SetSemanticFunction(&RiscVZfhCvtHd);
UnaryOpWithFflagsFPTestHelper<HalfFP, double>(
"fcvt.h.d", instruction_, {"f", "f"}, 32,
[](double input_double, int rm) -> std::tuple<HalfFP, uint32_t> {
uint32_t fflags = 0;
HalfFP half_result = FpConversionsTestHelper(input_double)
.ConvertWithFlags<HalfFP>(
fflags, static_cast<FPRoundingMode>(rm));
return std::make_tuple(half_result, fflags);
});
}
// A test to make sure +0 and -0 are converted correctly. Mutation testing
// inspired this test.
TEST_F(RV32ZfhInstructionTest, RiscVZfhCvtSh_strict_zeros) {
semantic_function_ = &RiscVZfhCvtSh;
uint32_t expected_p0 = FPTypeInfo<float>::kPosZero;
uint32_t expected_n0 = FPTypeInfo<float>::kNegZero;
float actual_p0;
float actual_n0;
HalfFP pos_zero = {.value = FPTypeInfo<HalfFP>::kPosZero};
HalfFP neg_zero = {.value = FPTypeInfo<HalfFP>::kNegZero};
actual_p0 =
ConversionHelper<float, HalfFP, kRoundingModeRoundToNearest>(pos_zero);
actual_n0 =
ConversionHelper<float, HalfFP, kRoundingModeRoundToNearest>(neg_zero);
EXPECT_EQ(absl::bit_cast<uint32_t>(actual_p0), expected_p0);
EXPECT_EQ(absl::bit_cast<uint32_t>(actual_n0), expected_n0);
actual_p0 =
ConversionHelper<float, HalfFP, kRoundingModeRoundTowardsZero>(pos_zero);
actual_n0 =
ConversionHelper<float, HalfFP, kRoundingModeRoundTowardsZero>(neg_zero);
EXPECT_EQ(absl::bit_cast<uint32_t>(actual_p0), expected_p0);
EXPECT_EQ(absl::bit_cast<uint32_t>(actual_n0), expected_n0);
actual_p0 = ConversionHelper<float, HalfFP, kRoundingModeRoundDown>(pos_zero);
actual_n0 = ConversionHelper<float, HalfFP, kRoundingModeRoundDown>(neg_zero);
EXPECT_EQ(absl::bit_cast<uint32_t>(actual_p0), expected_p0);
EXPECT_EQ(absl::bit_cast<uint32_t>(actual_n0), expected_n0);
actual_p0 = ConversionHelper<float, HalfFP, kRoundingModeRoundUp>(pos_zero);
actual_n0 = ConversionHelper<float, HalfFP, kRoundingModeRoundUp>(neg_zero);
EXPECT_EQ(absl::bit_cast<uint32_t>(actual_p0), expected_p0);
EXPECT_EQ(absl::bit_cast<uint32_t>(actual_n0), expected_n0);
}
// A test to make sure +0 and -0 are converted correctly. Mutation testing
// inspired this test.
TEST_F(RV32ZfhInstructionTest, RiscVZfhCvtHs_strict_zeros) {
semantic_function_ = &RiscVZfhCvtHs;
uint16_t expected_p0 = FPTypeInfo<HalfFP>::kPosZero;
uint16_t expected_n0 = FPTypeInfo<HalfFP>::kNegZero;
HalfFP actual_p0;
HalfFP actual_n0;
float pos_zero = absl::bit_cast<float>(FPTypeInfo<float>::kPosZero);
float neg_zero = absl::bit_cast<float>(FPTypeInfo<float>::kNegZero);
actual_p0 =
ConversionHelper<HalfFP, float, kRoundingModeRoundToNearest>(pos_zero);
actual_n0 =
ConversionHelper<HalfFP, float, kRoundingModeRoundToNearest>(neg_zero);
EXPECT_EQ(absl::bit_cast<uint16_t>(actual_p0), expected_p0);
EXPECT_EQ(absl::bit_cast<uint16_t>(actual_n0), expected_n0);
actual_p0 =
ConversionHelper<HalfFP, float, kRoundingModeRoundTowardsZero>(pos_zero);
actual_n0 =
ConversionHelper<HalfFP, float, kRoundingModeRoundTowardsZero>(neg_zero);
EXPECT_EQ(absl::bit_cast<uint16_t>(actual_p0), expected_p0);
EXPECT_EQ(absl::bit_cast<uint16_t>(actual_n0), expected_n0);
actual_p0 = ConversionHelper<HalfFP, float, kRoundingModeRoundDown>(pos_zero);
actual_n0 = ConversionHelper<HalfFP, float, kRoundingModeRoundDown>(neg_zero);
EXPECT_EQ(absl::bit_cast<uint16_t>(actual_p0), expected_p0);
EXPECT_EQ(absl::bit_cast<uint16_t>(actual_n0), expected_n0);
actual_p0 = ConversionHelper<HalfFP, float, kRoundingModeRoundUp>(pos_zero);
actual_n0 = ConversionHelper<HalfFP, float, kRoundingModeRoundUp>(neg_zero);
EXPECT_EQ(absl::bit_cast<uint16_t>(actual_p0), expected_p0);
EXPECT_EQ(absl::bit_cast<uint16_t>(actual_n0), expected_n0);
}
} // namespace