| // 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 <cassert> |
| #include <cmath> |
| #include <cstdint> |
| #include <functional> |
| #include <limits> |
| #include <type_traits> |
| |
| #include "absl/base/casts.h" |
| #include "absl/log/log.h" |
| #include "mpact/sim/generic/instruction.h" |
| #include "mpact/sim/generic/register.h" |
| #include "mpact/sim/generic/type_helpers.h" |
| #include "riscv/riscv_csr.h" |
| #include "riscv/riscv_fp_host.h" |
| #include "riscv/riscv_fp_info.h" |
| #include "riscv/riscv_fp_state.h" |
| #include "riscv/riscv_instruction_helpers.h" |
| #include "riscv/riscv_register.h" |
| #include "riscv/riscv_state.h" |
| |
| namespace mpact { |
| namespace sim { |
| namespace riscv { |
| |
| using HalfFP = ::mpact::sim::generic::HalfFP; |
| using ::mpact::sim::generic::IsMpactFp; |
| using ::mpact::sim::generic::operator*; // NOLINT: is used below (clang error). |
| |
| namespace { |
| |
| template <typename T> |
| struct DataTypeRegValue {}; |
| |
| template <> |
| struct DataTypeRegValue<float> { |
| using type = RVFpRegister::ValueType; |
| }; |
| |
| template <> |
| struct DataTypeRegValue<double> { |
| using type = RVFpRegister::ValueType; |
| }; |
| |
| template <> |
| struct DataTypeRegValue<HalfFP> { |
| using type = RVFpRegister::ValueType; |
| }; |
| |
| template <> |
| struct DataTypeRegValue<int32_t> { |
| using type = RV32Register::ValueType; |
| }; |
| |
| template <> |
| struct DataTypeRegValue<uint32_t> { |
| using type = RV32Register::ValueType; |
| }; |
| |
| template <> |
| struct DataTypeRegValue<int64_t> { |
| using type = RV64Register::ValueType; |
| }; |
| |
| template <> |
| struct DataTypeRegValue<uint64_t> { |
| using type = RV64Register::ValueType; |
| }; |
| |
| // Convert from half precision to single or double precision. |
| template <typename T> |
| inline T ConvertFromHalfFP(HalfFP half_fp, uint32_t& fflags) { |
| using UIntType = typename FPTypeInfo<T>::UIntType; |
| using HalfFPUIntType = typename FPTypeInfo<HalfFP>::UIntType; |
| HalfFPUIntType in_int = half_fp.value; |
| |
| if (FPTypeInfo<HalfFP>::IsNaN(half_fp)) { |
| if (FPTypeInfo<HalfFP>::IsSNaN(half_fp)) { |
| fflags |= static_cast<uint32_t>(FPExceptions::kInvalidOp); |
| } |
| UIntType uint_value = FPTypeInfo<T>::kCanonicalNaN; |
| return absl::bit_cast<T>(uint_value); |
| } |
| |
| if (FPTypeInfo<HalfFP>::IsInf(half_fp)) { |
| UIntType uint_value = FPTypeInfo<T>::kPosInf; |
| UIntType sign = in_int >> (FPTypeInfo<HalfFP>::kBitSize - 1); |
| uint_value |= sign << (FPTypeInfo<T>::kBitSize - 1); |
| return absl::bit_cast<T>(uint_value); |
| } |
| |
| if ((in_int == 0) || (in_int == (1 << (FPTypeInfo<HalfFP>::kBitSize - 1)))) { |
| UIntType uint_value = |
| static_cast<UIntType>(in_int) |
| << (FPTypeInfo<T>::kBitSize - FPTypeInfo<HalfFP>::kBitSize); |
| return absl::bit_cast<T>(uint_value); |
| } |
| |
| UIntType in_sign = FPTypeInfo<HalfFP>::SignBit(half_fp); |
| UIntType in_exp = |
| (in_int & FPTypeInfo<HalfFP>::kExpMask) >> FPTypeInfo<HalfFP>::kSigSize; |
| UIntType in_sig = in_int & FPTypeInfo<HalfFP>::kSigMask; |
| UIntType out_int = 0; |
| UIntType out_sig = in_sig; |
| if ((in_exp == 0) && (in_sig != 0)) { |
| // Handle subnormal half precision inputs. They always result in a normal |
| // float or double. Calculate how much shifting is needed move the MSB to |
| // the location of the implicit bit. Then it can be handled as a normal |
| // value from here on. |
| int32_t shift_count = |
| (1 + FPTypeInfo<HalfFP>::kSigSize) - |
| (std::numeric_limits<UIntType>::digits - absl::countl_zero(out_sig)); |
| out_sig = (out_sig << shift_count) & FPTypeInfo<HalfFP>::kSigMask; |
| in_exp = 1 - shift_count; |
| } |
| out_int |= in_sign << (FPTypeInfo<T>::kBitSize - 1); |
| out_int |= (in_exp + FPTypeInfo<T>::kExpBias - FPTypeInfo<HalfFP>::kExpBias) |
| << FPTypeInfo<T>::kSigSize; |
| out_int |= |
| out_sig << (FPTypeInfo<T>::kSigSize - FPTypeInfo<HalfFP>::kSigSize); |
| return absl::bit_cast<T>(out_int); |
| } |
| |
| // This is a soft conversion from a float or double to a half precision value. |
| // It is not a direct conversion from the floating point format to the half |
| // format. Instead, it uses the floating point hardware to do the conversion. |
| // This is done to get the correct rounding behavior for free from the FPU. |
| template <typename T> |
| inline HalfFP ConvertToHalfFP(T input_value, FPRoundingMode rm, |
| uint32_t& fflags) { |
| using UIntType = typename FPTypeInfo<T>::UIntType; |
| using IntType = typename FPTypeInfo<T>::IntType; |
| UIntType in_int = absl::bit_cast<UIntType>(input_value); |
| HalfFP half_fp = {.value = 0x0000}; |
| |
| // Extract the mantissa, exponent and sign. |
| UIntType mantissa = in_int & FPTypeInfo<T>::kSigMask; |
| UIntType exponent = |
| (in_int & FPTypeInfo<T>::kExpMask) >> FPTypeInfo<T>::kSigSize; |
| UIntType sign = in_int >> (FPTypeInfo<T>::kBitSize - 1); |
| |
| if (std::isnan(input_value)) { |
| half_fp.value = FPTypeInfo<HalfFP>::kCanonicalNaN; |
| if (FPTypeInfo<T>::IsSNaN(input_value)) { |
| fflags |= static_cast<UIntType>(FPExceptions::kInvalidOp); |
| } |
| return half_fp; |
| } |
| |
| if (std::isinf(input_value)) { |
| half_fp.value = FPTypeInfo<HalfFP>::kPosInf; |
| half_fp.value |= (sign & 1) << (FPTypeInfo<HalfFP>::kBitSize - 1); |
| return half_fp; |
| } |
| |
| if ((in_int == 0) || (in_int == (1ULL << (FPTypeInfo<T>::kBitSize - 1)))) { |
| half_fp.value = |
| in_int >> (FPTypeInfo<T>::kBitSize - FPTypeInfo<HalfFP>::kBitSize); |
| return half_fp; |
| } |
| |
| IntType bias_diff = FPTypeInfo<T>::kExpBias - FPTypeInfo<HalfFP>::kExpBias; |
| IntType unbounded_half_exponent = static_cast<IntType>(exponent) - bias_diff; |
| IntType sig_size_diff = |
| FPTypeInfo<T>::kSigSize - FPTypeInfo<HalfFP>::kSigSize; |
| UIntType half_inf_exponent = ((1 << FPTypeInfo<HalfFP>::kExpSize) - 1); |
| UIntType source_type_inf_exponent = ((1 << FPTypeInfo<T>::kExpSize) - 1); |
| |
| // Create a temp float with the smallest normal exponent and input mantissa. |
| T ftmp = absl::bit_cast<T>( |
| (sign << (FPTypeInfo<T>::kBitSize - 1)) | |
| (static_cast<UIntType>(1ULL) << FPTypeInfo<T>::kSigSize) | mantissa); |
| |
| // Create a divisor float that will be used for shifting the mantissa in a |
| // rounding aware way. The amount of shifting depends on if the result is |
| // subnormal or normal. |
| T fdiv = 0; |
| UIntType default_fdiv_exp = FPTypeInfo<T>::kExpBias + sig_size_diff; |
| UIntType fdiv_exp = default_fdiv_exp; |
| if (unbounded_half_exponent > 0) { |
| fdiv_exp = default_fdiv_exp; |
| } else if (unbounded_half_exponent < 0) { |
| // shift_count: emin - unbiased exponent |
| IntType shift_count = 1 - static_cast<int>(exponent) + bias_diff; |
| fdiv_exp = default_fdiv_exp + shift_count; |
| fdiv_exp = std::min(fdiv_exp, source_type_inf_exponent - 1); |
| } else { |
| fdiv_exp = default_fdiv_exp + 1; |
| } |
| fdiv = absl::bit_cast<T>(fdiv_exp << FPTypeInfo<T>::kSigSize); |
| |
| // Shift right by doing division. |
| T fres = ftmp / fdiv; |
| UIntType res = absl::bit_cast<UIntType>(fres); |
| |
| // Shift left by doing multiplication. |
| T fmultiply = absl::bit_cast<T>(default_fdiv_exp << FPTypeInfo<T>::kSigSize); |
| T fres2 = fres * fmultiply; |
| UIntType res2 = absl::bit_cast<UIntType>(fres2); |
| |
| // Update the exponent if rounding caused an increase. |
| IntType exp_diff = static_cast<IntType>((res2 >> FPTypeInfo<T>::kSigSize) & |
| source_type_inf_exponent) - |
| 1; |
| UIntType new_exponent = (exponent + exp_diff) & source_type_inf_exponent; |
| |
| UIntType half_exponent = 0; |
| if (unbounded_half_exponent > 0) { |
| half_exponent = new_exponent - bias_diff; |
| } else if (unbounded_half_exponent < 0) { |
| // Guaranteed subnormal. Nothing to do. |
| } else { |
| // This case could be normal or subnormal depending on the rounding result. |
| half_exponent = (res2 >> FPTypeInfo<T>::kSigSize) & half_inf_exponent; |
| } |
| |
| UIntType half_mantissa = |
| (res2 >> sig_size_diff) & FPTypeInfo<HalfFP>::kSigMask; |
| if (unbounded_half_exponent < 0) { // Guaranteed Subnormal |
| half_mantissa = (res & (1 << FPTypeInfo<HalfFP>::kSigSize)) |
| ? ((res >> 1) & FPTypeInfo<HalfFP>::kSigMask) |
| : res & FPTypeInfo<HalfFP>::kSigMask; |
| } |
| |
| // Handle the rules for overflowing to infinity depending on the rounding |
| // mode. |
| if (half_exponent >= half_inf_exponent) { |
| fflags |= static_cast<uint32_t>(FPExceptions::kOverflow); |
| fflags |= static_cast<uint32_t>(FPExceptions::kInexact); |
| switch (rm) { |
| case FPRoundingMode::kRoundToNearest: |
| half_exponent = half_inf_exponent; |
| half_mantissa = 0; |
| break; |
| case FPRoundingMode::kRoundTowardsZero: |
| half_exponent = half_inf_exponent - 1; |
| half_mantissa = FPTypeInfo<HalfFP>::kSigMask; |
| break; |
| case FPRoundingMode::kRoundDown: |
| half_exponent = sign ? half_inf_exponent : half_inf_exponent - 1; |
| half_mantissa = sign ? 0 : FPTypeInfo<HalfFP>::kSigMask; |
| break; |
| case FPRoundingMode::kRoundUp: |
| half_exponent = sign ? half_inf_exponent - 1 : half_inf_exponent; |
| half_mantissa = sign ? FPTypeInfo<HalfFP>::kSigMask : 0; |
| break; |
| default: |
| half_exponent = half_inf_exponent; |
| half_mantissa = 0; |
| break; |
| } |
| } |
| |
| // Construct the half float. |
| half_fp.value = half_mantissa | |
| (half_exponent << FPTypeInfo<HalfFP>::kSigSize) | |
| (sign << (FPTypeInfo<HalfFP>::kBitSize - 1)); |
| |
| uint32_t temp_fflags = 0; |
| T reconstructed_value = ConvertFromHalfFP<T>(half_fp, temp_fflags); |
| bool exact_conversion = reconstructed_value == input_value; |
| |
| // Handle flags for the specific underflow case. |
| if (!exact_conversion && |
| ((unbounded_half_exponent < 0) || |
| ((unbounded_half_exponent == 0) && (fres2 != ftmp)))) { |
| fflags |= static_cast<uint32_t>(FPExceptions::kUnderflow); |
| } |
| |
| // Handle flags for the specific inexact case. |
| if (!exact_conversion && (fres2 != ftmp)) { |
| fflags |= static_cast<uint32_t>(FPExceptions::kInexact); |
| } |
| return half_fp; |
| } |
| |
| template <typename Result, typename Argument> |
| void RiscVZfhCvtHelper( |
| const Instruction* instruction, |
| std::function<Result(Argument, FPRoundingMode, uint32_t&)> operation) { |
| RiscVCsrDestinationOperand* fflags_dest = |
| static_cast<RiscVCsrDestinationOperand*>(instruction->Destination(1)); |
| using DstRegValue = typename DataTypeRegValue<Result>::type; |
| uint32_t fflags = 0; |
| |
| Argument lhs; |
| if constexpr (IsMpactFp<Argument>::value) { |
| lhs = GetNaNBoxedSource<RVFpRegister::ValueType, Argument>(instruction, 0); |
| if (FPTypeInfo<Argument>::IsSNaN(lhs)) { |
| fflags |= *FPExceptions::kInvalidOp; |
| } |
| } else { |
| lhs = generic::GetInstructionSource<Argument>(instruction, 0); |
| } |
| // Get the rounding mode. |
| int rm_value = generic::GetInstructionSource<int>(instruction, 1); |
| |
| auto* rv_fp = static_cast<RiscVState*>(instruction->state())->rv_fp(); |
| // If the rounding mode is dynamic, read it from the current state. |
| if (rm_value == *FPRoundingMode::kDynamic) { |
| if (!rv_fp->rounding_mode_valid()) { |
| LOG(ERROR) << "Invalid rounding mode"; |
| return; |
| } |
| rm_value = *rv_fp->GetRoundingMode(); |
| } |
| |
| Result dest_value; |
| { |
| ScopedFPRoundingMode scoped_rm(rv_fp->host_fp_interface(), rm_value); |
| dest_value = operation(lhs, static_cast<FPRoundingMode>(rm_value), fflags); |
| } |
| fflags_dest->GetRiscVCsr()->SetBits(fflags); |
| auto* reg = static_cast<generic::RegisterDestinationOperand<DstRegValue>*>( |
| instruction->Destination(0)) |
| ->GetRegister(); |
| |
| if (sizeof(DstRegValue) > sizeof(Result) && IsMpactFp<Result>::value) { |
| // If the floating point value is narrower than the register, the upper |
| // bits have to be set to all ones. |
| using UReg = typename std::make_unsigned<DstRegValue>::type; |
| using UInt = typename FPTypeInfo<Result>::UIntType; |
| auto dest_u_value = *reinterpret_cast<UInt*>(&dest_value); |
| UReg reg_value = std::numeric_limits<UReg>::max(); |
| int shift = 8 * sizeof(Result); |
| reg_value = (reg_value << shift) | dest_u_value; |
| reg->data_buffer()->template Set<DstRegValue>(0, reg_value); |
| return; |
| } |
| reg->data_buffer()->template Set<Result>(0, dest_value); |
| } |
| |
| // Generic helper function enabling HalfFP operations in native datatypes. |
| template <typename Argument, typename IntermediateType> |
| void RiscVZfhUnaryHelper( |
| const Instruction* instruction, |
| std::function<IntermediateType(IntermediateType)> operation) { |
| RiscVCsrDestinationOperand* fflags_dest = |
| static_cast<RiscVCsrDestinationOperand*>(instruction->Destination(1)); |
| uint32_t fflags = 0; |
| RiscVUnaryFloatNaNBoxOp<RVFpRegister::ValueType, RVFpRegister::ValueType, |
| HalfFP, Argument>( |
| instruction, [instruction, &operation, &fflags](Argument a) -> HalfFP { |
| RiscVFPState* rv_fp = |
| static_cast<RiscVState*>(instruction->state())->rv_fp(); |
| int rm_value = generic::GetInstructionSource<int>(instruction, 1); |
| |
| // If the rounding mode is dynamic, read it from the current state. |
| if (rm_value == *FPRoundingMode::kDynamic) { |
| if (!rv_fp->rounding_mode_valid()) { |
| LOG(ERROR) << "Invalid rounding mode"; |
| } |
| rm_value = *(rv_fp->GetRoundingMode()); |
| } |
| FPRoundingMode rm = static_cast<FPRoundingMode>(rm_value); |
| IntermediateType argument1 = |
| ConvertFromHalfFP<IntermediateType>(a, fflags); |
| IntermediateType result; |
| { |
| ScopedFPStatus set_fpstatus(rv_fp->host_fp_interface(), rm); |
| result = operation(argument1); |
| } |
| // To get the correct fflags we need a combination of host flags from |
| // the operation and the conversion flags. Copy the host flags and merge |
| // them with the conversion flags. |
| fflags |= rv_fp->fflags()->GetUint32(); |
| { |
| // ConvertToHalfFP pollutes the host flags so we need to create a |
| // ScopedFPRoundingMode to restore the host flags. |
| ScopedFPRoundingMode scoped_rm(rv_fp->host_fp_interface(), rm_value); |
| return ConvertToHalfFP(result, rm, fflags); |
| } |
| }); |
| fflags_dest->GetRiscVCsr()->SetBits(fflags); |
| } |
| |
| // Generic helper function enabling HalfFP operations in native datatypes. |
| template <typename Argument, typename IntermediateType> |
| void RiscVZfhBinaryHelper( |
| const Instruction* instruction, |
| std::function<IntermediateType(IntermediateType, IntermediateType)> |
| operation) { |
| RiscVCsrDestinationOperand* fflags_dest = |
| static_cast<RiscVCsrDestinationOperand*>(instruction->Destination(1)); |
| uint32_t fflags = 0; |
| RiscVBinaryFloatNaNBoxOp<RVFpRegister::ValueType, HalfFP, Argument>( |
| instruction, |
| [instruction, &operation, &fflags](Argument a, Argument b) -> HalfFP { |
| RiscVFPState* rv_fp = |
| static_cast<RiscVState*>(instruction->state())->rv_fp(); |
| int rm_value = generic::GetInstructionSource<int>(instruction, 2); |
| // If the rounding mode is dynamic, read it from the current state. |
| if (rm_value == *FPRoundingMode::kDynamic) { |
| if (!rv_fp->rounding_mode_valid()) { |
| LOG(ERROR) << "Invalid rounding mode"; |
| } |
| rm_value = *(rv_fp->GetRoundingMode()); |
| } |
| FPRoundingMode rm = static_cast<FPRoundingMode>(rm_value); |
| IntermediateType argument1 = |
| ConvertFromHalfFP<IntermediateType>(a, fflags); |
| IntermediateType argument2 = |
| ConvertFromHalfFP<IntermediateType>(b, fflags); |
| IntermediateType result; |
| { |
| ScopedFPStatus set_fpstatus(rv_fp->host_fp_interface(), rm); |
| result = operation(argument1, argument2); |
| } |
| // To get the correct fflags we need a combination of host flags from |
| // the operation and the conversion flags. Copy the host flags and merge |
| // them with the conversion flags. |
| fflags |= rv_fp->fflags()->GetUint32(); |
| { |
| // ConvertToHalfFP pollutes the host flags so we need to create a |
| // ScopedFPRoundingMode to restore the host flags. |
| ScopedFPRoundingMode scoped_rm(rv_fp->host_fp_interface(), rm_value); |
| return ConvertToHalfFP(result, rm, fflags); |
| } |
| }); |
| fflags_dest->GetRiscVCsr()->SetBits(fflags); |
| } |
| |
| // Generic helper function enabling HalfFP operations in native datatypes. |
| template <typename Argument, typename IntermediateType> |
| void RiscVZfhTernaryHelper( |
| const Instruction* instruction, |
| std::function<IntermediateType(IntermediateType, IntermediateType, |
| IntermediateType)> |
| operation) { |
| RiscVCsrDestinationOperand* fflags_dest = |
| static_cast<RiscVCsrDestinationOperand*>(instruction->Destination(1)); |
| uint32_t fflags = 0; |
| // RiscVTernaryFloatNaNBoxOp will handle the register NaN boxed reads and |
| // write. The operation is in a native datatype so we will handle conversions |
| // from/to half precision float values before and after the operation. |
| RiscVTernaryFloatNaNBoxOp<RVFpRegister::ValueType, HalfFP, Argument>( |
| instruction, |
| [instruction, &operation, &fflags](Argument a, Argument b, |
| Argument c) -> HalfFP { |
| RiscVFPState* rv_fp = |
| static_cast<RiscVState*>(instruction->state())->rv_fp(); |
| int rm_value = generic::GetInstructionSource<int>(instruction, 3); |
| // If the rounding mode is dynamic, read it from the current state. |
| if (rm_value == *FPRoundingMode::kDynamic) { |
| if (!rv_fp->rounding_mode_valid()) { |
| LOG(ERROR) << "Invalid rounding mode"; |
| } |
| rm_value = *(rv_fp->GetRoundingMode()); |
| } |
| FPRoundingMode rm = static_cast<FPRoundingMode>(rm_value); |
| IntermediateType argument1 = |
| ConvertFromHalfFP<IntermediateType>(a, fflags); |
| IntermediateType argument2 = |
| ConvertFromHalfFP<IntermediateType>(b, fflags); |
| IntermediateType argument3 = |
| ConvertFromHalfFP<IntermediateType>(c, fflags); |
| IntermediateType result; |
| { |
| ScopedFPStatus set_fpstatus(rv_fp->host_fp_interface(), rm_value); |
| result = operation(argument1, argument2, argument3); |
| } |
| // To get the correct fflags we need a combination of host flags from |
| // the operation and the conversion flags. Copy the host flags and merge |
| // them with the conversion flags. |
| fflags |= rv_fp->fflags()->GetUint32(); |
| { |
| // ConvertToHalfFP pollutes the host flags so we need to create a |
| // ScopedFPRoundingMode to restore the host flags. |
| ScopedFPRoundingMode scoped_rm(rv_fp->host_fp_interface(), rm_value); |
| return ConvertToHalfFP(result, rm, fflags); |
| } |
| }); |
| fflags_dest->GetRiscVCsr()->SetBits(fflags); |
| } |
| |
| // Move a half precision value from a float register to an integer register. |
| template <typename XRegister> |
| void RiscVZfhFMvxhHelper(const Instruction* instruction) { |
| using XRegValue = typename XRegister::ValueType; |
| RiscVUnaryFloatOp<XRegValue, HalfFP>(instruction, [](HalfFP a) -> XRegValue { |
| if (FPTypeInfo<HalfFP>::SignBit(a)) { |
| // Repeat the sign bit for negative values. |
| return (std::numeric_limits<XRegValue>::max() << 16) | a.value; |
| } |
| return static_cast<XRegValue>(a.value); |
| }); |
| } |
| |
| // Move a half precision value from an integer register to a float register |
| template <typename XRegister> |
| inline void RiscVZfhFMvhxHelper(const Instruction* instruction) { |
| using DstRegValue = typename RVFpRegister::ValueType; |
| using SrcRegValue = typename XRegister::ValueType; |
| SrcRegValue lhs = generic::GetInstructionSource<SrcRegValue>(instruction, 0); |
| HalfFP dest_value = {.value = static_cast<uint16_t>(lhs)}; |
| |
| auto* reg = static_cast<generic::RegisterDestinationOperand<DstRegValue>*>( |
| instruction->Destination(0)) |
| ->GetRegister(); |
| |
| // NaN box the value. |
| using UReg = typename std::make_unsigned<DstRegValue>::type; |
| using UInt = typename FPTypeInfo<HalfFP>::UIntType; |
| auto dest_u_value = *reinterpret_cast<UInt*>(&dest_value); |
| UReg reg_value = std::numeric_limits<UReg>::max(); |
| int shift = 8 * sizeof(HalfFP); |
| reg_value = (reg_value << shift) | dest_u_value; |
| reg->data_buffer()->template Set<DstRegValue>(0, reg_value); |
| } |
| |
| // Compare two half precision values for equality. |
| template <typename XRegister> |
| inline void RiscVZfhFcmpeqHelper(const Instruction* instruction) { |
| using DstRegValue = typename XRegister::ValueType; |
| uint32_t fflags = 0; |
| HalfFP lhs = |
| GetNaNBoxedSource<RVFpRegister::ValueType, HalfFP>(instruction, 0); |
| HalfFP rhs = |
| GetNaNBoxedSource<RVFpRegister::ValueType, HalfFP>(instruction, 1); |
| float lhs_f = ConvertFromHalfFP<float>(lhs, fflags); |
| float rhs_f = ConvertFromHalfFP<float>(rhs, fflags); |
| |
| DstRegValue result = lhs_f == rhs_f ? 1 : 0; |
| auto* reg = static_cast<generic::RegisterDestinationOperand<DstRegValue>*>( |
| instruction->Destination(0)) |
| ->GetRegister(); |
| reg->data_buffer()->template Set<DstRegValue>(0, result); |
| |
| RiscVCsrDestinationOperand* fflags_dest = |
| static_cast<RiscVCsrDestinationOperand*>(instruction->Destination(1)); |
| fflags_dest->GetRiscVCsr()->SetBits(fflags & *FPExceptions::kInvalidOp); |
| } |
| |
| // Compare two half precision values for less than. |
| template <typename XRegister> |
| inline void RiscVZfhFcmpltHelper(const Instruction* instruction) { |
| using DstRegValue = typename XRegister::ValueType; |
| uint32_t unused_fflags = 0; |
| HalfFP lhs = |
| GetNaNBoxedSource<RVFpRegister::ValueType, HalfFP>(instruction, 0); |
| HalfFP rhs = |
| GetNaNBoxedSource<RVFpRegister::ValueType, HalfFP>(instruction, 1); |
| float lhs_f = ConvertFromHalfFP<float>(lhs, unused_fflags); |
| float rhs_f = ConvertFromHalfFP<float>(rhs, unused_fflags); |
| |
| DstRegValue result = lhs_f < rhs_f ? 1 : 0; |
| auto* reg = static_cast<generic::RegisterDestinationOperand<DstRegValue>*>( |
| instruction->Destination(0)) |
| ->GetRegister(); |
| reg->data_buffer()->template Set<DstRegValue>(0, result); |
| |
| RiscVCsrDestinationOperand* fflags_dest = |
| static_cast<RiscVCsrDestinationOperand*>(instruction->Destination(1)); |
| if (std::isnan(lhs_f) || std::isnan(rhs_f)) { |
| fflags_dest->GetRiscVCsr()->SetBits(*FPExceptions::kInvalidOp); |
| } |
| } |
| |
| // Compare two half precision values for less than or equal to. |
| template <typename XRegister> |
| void RiscVZfhFcmpleHelper(const Instruction* instruction) { |
| using DstRegValue = typename XRegister::ValueType; |
| uint32_t unused_fflags = 0; |
| HalfFP lhs = |
| GetNaNBoxedSource<RVFpRegister::ValueType, HalfFP>(instruction, 0); |
| HalfFP rhs = |
| GetNaNBoxedSource<RVFpRegister::ValueType, HalfFP>(instruction, 1); |
| float lhs_f = ConvertFromHalfFP<float>(lhs, unused_fflags); |
| float rhs_f = ConvertFromHalfFP<float>(rhs, unused_fflags); |
| |
| DstRegValue result = lhs_f <= rhs_f ? 1 : 0; |
| auto* reg = static_cast<generic::RegisterDestinationOperand<DstRegValue>*>( |
| instruction->Destination(0)) |
| ->GetRegister(); |
| reg->data_buffer()->template Set<DstRegValue>(0, result); |
| |
| RiscVCsrDestinationOperand* fflags_dest = |
| static_cast<RiscVCsrDestinationOperand*>(instruction->Destination(1)); |
| if (std::isnan(lhs_f) || std::isnan(rhs_f)) { |
| fflags_dest->GetRiscVCsr()->SetBits(*FPExceptions::kInvalidOp); |
| } |
| } |
| |
| } // namespace |
| |
| namespace RV32 { |
| // Move a half precision value from a float register to a 32 bit integer |
| // register. |
| void RiscVZfhFMvxh(const Instruction* instruction) { |
| RiscVZfhFMvxhHelper<RV32Register>(instruction); |
| } |
| |
| // Move a half precision value from an integer register to a float register and |
| // NaN box the value. |
| void RiscVZfhFMvhx(const Instruction* instruction) { |
| RiscVZfhFMvhxHelper<RV32Register>(instruction); |
| } |
| |
| // Convert from half precision to signed 32 bit integer. |
| void RiscVZfhCvtWh(const Instruction* instruction) { |
| RiscVConvertFloatWithFflagsOp<typename RV32Register::ValueType, HalfFP, |
| int32_t>(instruction); |
| } |
| |
| // Convert from half precision to unsigned 32 bit integer. |
| void RiscVZfhCvtWuh(const Instruction* instruction) { |
| RiscVConvertFloatWithFflagsOp<typename RV32Register::ValueType, HalfFP, |
| uint32_t>(instruction); |
| } |
| |
| // Compare two half precision values for equality. |
| void RiscVZfhFcmpeq(const Instruction* instruction) { |
| RiscVZfhFcmpeqHelper<RV32Register>(instruction); |
| } |
| |
| // Compare two half precision values for less than. |
| void RiscVZfhFcmplt(const Instruction* instruction) { |
| RiscVZfhFcmpltHelper<RV32Register>(instruction); |
| } |
| |
| // Compare two half precision values for less than or equal to. |
| void RiscVZfhFcmple(const Instruction* instruction) { |
| RiscVZfhFcmpleHelper<RV32Register>(instruction); |
| } |
| |
| // Classify a half precision value. |
| void RiscVZfhFclass(const Instruction* instruction) { |
| RiscVUnaryOp<RV32Register, uint32_t, HalfFP>( |
| instruction, [](HalfFP a) -> uint32_t { |
| return static_cast<uint32_t>(ClassifyFP(a)); |
| }); |
| } |
| |
| } // namespace RV32 |
| |
| namespace RV64 { |
| // Move a half precision value from a float register to a 32 bit integer |
| // register. |
| void RiscVZfhFMvxh(const Instruction* instruction) { |
| RiscVZfhFMvxhHelper<RV64Register>(instruction); |
| } |
| |
| // Move a half precision value from an integer register to a float register and |
| // NaN box the value. |
| void RiscVZfhFMvhx(const Instruction* instruction) { |
| RiscVZfhFMvhxHelper<RV64Register>(instruction); |
| } |
| |
| // Convert from half precision to signed 32 bit integer. |
| void RiscVZfhCvtWh(const Instruction* instruction) { |
| RiscVConvertFloatWithFflagsOp<typename RV64Register::ValueType, HalfFP, |
| int32_t>(instruction); |
| } |
| |
| // Convert from half precision to unsigned 32 bit integer. |
| void RiscVZfhCvtWuh(const Instruction* instruction) { |
| RiscVConvertFloatWithFflagsOp<typename RV64Register::ValueType, HalfFP, |
| uint32_t>(instruction); |
| } |
| |
| // Compare two half precision values for equality. |
| void RiscVZfhFcmpeq(const Instruction* instruction) { |
| RiscVZfhFcmpeqHelper<RV64Register>(instruction); |
| } |
| |
| // Compare two half precision values for less than. |
| void RiscVZfhFcmplt(const Instruction* instruction) { |
| RiscVZfhFcmpltHelper<RV64Register>(instruction); |
| } |
| |
| // Compare two half precision values for less than or equal to. |
| void RiscVZfhFcmple(const Instruction* instruction) { |
| RiscVZfhFcmpleHelper<RV64Register>(instruction); |
| } |
| |
| // Classify a half precision value. |
| void RiscVZfhFclass(const Instruction* instruction) { |
| RiscVUnaryOp<RV64Register, uint64_t, HalfFP>( |
| instruction, [](HalfFP a) -> uint64_t { |
| return static_cast<uint64_t>(ClassifyFP(a)); |
| }); |
| } |
| |
| // Converts from half precision to signed 64 bit integer. |
| void RiscVZfhCvtLh(const Instruction* instruction) { |
| RiscVConvertFloatWithFflagsOp<typename RV64Register::ValueType, HalfFP, |
| int64_t>(instruction); |
| } |
| |
| // Converts from half precision to unsigned 64 bit integer. |
| void RiscVZfhCvtLuh(const Instruction* instruction) { |
| RiscVConvertFloatWithFflagsOp<typename RV64Register::ValueType, HalfFP, |
| uint64_t>(instruction); |
| } |
| |
| // Converts from signed 64 bit integer to half precision. |
| void RiscVZfhCvtHl(const Instruction* instruction) { |
| RiscVZfhCvtHelper<HalfFP, int64_t>( |
| instruction, |
| [](int64_t a, FPRoundingMode rm, uint32_t& fflags) -> HalfFP { |
| return ConvertToHalfFP(static_cast<float>(a), rm, fflags); |
| }); |
| } |
| |
| // Convert from unsigned 64 bit integer to half precision. |
| void RiscVZfhCvtHlu(const Instruction* instruction) { |
| RiscVZfhCvtHelper<HalfFP, uint64_t>( |
| instruction, |
| [](uint64_t a, FPRoundingMode rm, uint32_t& fflags) -> HalfFP { |
| return ConvertToHalfFP(static_cast<float>(a), rm, fflags); |
| }); |
| } |
| |
| } // namespace RV64 |
| |
| void RiscVZfhFlhChild(const Instruction* instruction) { |
| using FPUInt = typename FPTypeInfo<HalfFP>::UIntType; |
| LoadContext* context = static_cast<LoadContext*>(instruction->context()); |
| auto value = context->value_db->Get<FPUInt>(0); |
| auto* reg = |
| static_cast< |
| generic::RegisterDestinationOperand<RVFpRegister::ValueType>*>( |
| instruction->Destination(0)) |
| ->GetRegister(); |
| if (sizeof(RVFpRegister::ValueType) > sizeof(FPUInt)) { |
| // NaN box the loaded value. |
| auto reg_value = std::numeric_limits<RVFpRegister::ValueType>::max(); |
| reg_value <<= sizeof(FPUInt) * 8; |
| reg_value |= value; |
| reg->data_buffer()->Set<RVFpRegister::ValueType>(0, reg_value); |
| return; |
| } |
| reg->data_buffer()->Set<RVFpRegister::ValueType>(0, value); |
| } |
| |
| // Convert from half precision to single precision. |
| void RiscVZfhCvtSh(const Instruction* instruction) { |
| RiscVZfhCvtHelper<float, HalfFP>( |
| instruction, [](HalfFP a, FPRoundingMode rm, uint32_t& fflags) -> float { |
| return ConvertFromHalfFP<float>(a, fflags); |
| }); |
| } |
| |
| // Convert from single precision to half precision. |
| void RiscVZfhCvtHs(const Instruction* instruction) { |
| RiscVZfhCvtHelper<HalfFP, float>( |
| instruction, [](float a, FPRoundingMode rm, uint32_t& fflags) -> HalfFP { |
| return ConvertToHalfFP(a, rm, fflags); |
| }); |
| } |
| |
| // Convert from half precision to double precision. |
| void RiscVZfhCvtDh(const Instruction* instruction) { |
| RiscVZfhCvtHelper<double, HalfFP>( |
| instruction, [](HalfFP a, FPRoundingMode rm, uint32_t& fflags) -> double { |
| return ConvertFromHalfFP<double>(a, fflags); |
| }); |
| } |
| |
| // Convert from double precision to half precision. |
| void RiscVZfhCvtHd(const Instruction* instruction) { |
| RiscVZfhCvtHelper<HalfFP, double>( |
| instruction, [](double a, FPRoundingMode rm, uint32_t& fflags) -> HalfFP { |
| return ConvertToHalfFP(a, rm, fflags); |
| }); |
| } |
| |
| // Add two half precision values. Do the calculation in single precision. |
| void RiscVZfhFadd(const Instruction* instruction) { |
| RiscVZfhBinaryHelper<HalfFP, float>( |
| instruction, [](float a, float b) -> float { return a + b; }); |
| } |
| |
| // Subtract two half precision values. Do the calculation in single precision. |
| void RiscVZfhFsub(const Instruction* instruction) { |
| RiscVZfhBinaryHelper<HalfFP, float>( |
| instruction, [](float a, float b) -> float { return a - b; }); |
| } |
| |
| // Multiply two half precision values. Do the calculation in single precision. |
| void RiscVZfhFmul(const Instruction* instruction) { |
| RiscVZfhBinaryHelper<HalfFP, float>( |
| instruction, [](float a, float b) -> float { return a * b; }); |
| } |
| |
| // Divide two half precision values. Do the calculation in single precision. |
| void RiscVZfhFdiv(const Instruction* instruction) { |
| RiscVZfhBinaryHelper<HalfFP, float>( |
| instruction, [](float a, float b) -> float { return a / b; }); |
| } |
| |
| // Take the minimum of two half precision values. Do the operation in single |
| // precision. |
| void RiscVZfhFmin(const Instruction* instruction) { |
| RiscVZfhBinaryHelper<HalfFP, float>(instruction, |
| [](float a, float b) -> float { |
| // On ARM std::fminf returns NaN if |
| // either input is NaN. Add extra checks |
| // to make X86 and ARM behavior the |
| // same. |
| if (std::isnan(a)) { |
| return b; |
| } else if (std::isnan(b)) { |
| return a; |
| } |
| return std::fminf(a, b); |
| }); |
| } |
| |
| // Take the maximum of two half precision values. Do the operation in single |
| // precision. |
| void RiscVZfhFmax(const Instruction* instruction) { |
| RiscVZfhBinaryHelper<HalfFP, float>(instruction, |
| [](float a, float b) -> float { |
| // On ARM std::fmaxf returns NaN if |
| // either input is NaN. Add extra checks |
| // to make X86 and ARM behavior the |
| // same. |
| if (std::isnan(a)) { |
| return b; |
| } else if (std::isnan(b)) { |
| return a; |
| } |
| return std::fmaxf(a, b); |
| }); |
| } |
| |
| // Calculate the square root of a half precision value. Do the operation in |
| // single precision and then convert back to half precision. |
| void RiscVZfhFsqrt(const Instruction* instruction) { |
| RiscVZfhUnaryHelper<HalfFP, float>( |
| instruction, [](float a) -> float { return std::sqrt(a); }); |
| } |
| |
| // The result is the exponent and significand of the first source with the |
| // sign bit of the second source. |
| void RiscVZfhFsgnj(const Instruction* instruction) { |
| RiscVBinaryFloatNaNBoxOp<RVFpRegister::ValueType, HalfFP, HalfFP>( |
| instruction, [](HalfFP a, HalfFP b) -> HalfFP { |
| uint16_t mask = |
| FPTypeInfo<HalfFP>::kExpMask | FPTypeInfo<HalfFP>::kSigMask; |
| return HalfFP{.value = static_cast<uint16_t>((a.value & mask) | |
| (b.value & ~mask))}; |
| }); |
| } |
| |
| // The result is the exponent and significand of the first source with the |
| // opposite sign bit of the second source. |
| void RiscVZfhFsgnjn(const Instruction* instruction) { |
| RiscVBinaryFloatNaNBoxOp<RVFpRegister::ValueType, HalfFP, HalfFP>( |
| instruction, [](HalfFP a, HalfFP b) -> HalfFP { |
| uint16_t mask = |
| FPTypeInfo<HalfFP>::kExpMask | FPTypeInfo<HalfFP>::kSigMask; |
| return HalfFP{.value = static_cast<uint16_t>((a.value & mask) | |
| (~b.value & ~mask))}; |
| }); |
| } |
| |
| // The result is the exponent and significand of the first source with the |
| // sign bit that is the exclusive or of the two source sign bits. |
| void RiscVZfhFsgnjx(const Instruction* instruction) { |
| RiscVBinaryFloatNaNBoxOp<RVFpRegister::ValueType, HalfFP, HalfFP>( |
| instruction, [](HalfFP a, HalfFP b) -> HalfFP { |
| uint16_t mask = |
| FPTypeInfo<HalfFP>::kExpMask | FPTypeInfo<HalfFP>::kSigMask; |
| return HalfFP{.value = static_cast<uint16_t>( |
| (a.value & mask) | ((a.value ^ b.value) & ~mask))}; |
| }); |
| } |
| |
| // Fused multiply add in half precision. Do the operation in single precision. |
| // (rs1 * rs2) + rs3 |
| void RiscVZfhFmadd(const Instruction* instruction) { |
| RiscVZfhTernaryHelper<HalfFP, float>( |
| instruction, |
| [](float a, float b, float c) -> float { return fma(a, b, c); }); |
| } |
| |
| // Fused multiply add in half precision. Do the operation in single precision. |
| // (rs1 * rs2) - rs3 |
| void RiscVZfhFmsub(const Instruction* instruction) { |
| RiscVZfhTernaryHelper<HalfFP, float>( |
| instruction, |
| [](float a, float b, float c) -> float { return fma(a, b, -c); }); |
| } |
| |
| // Fused multiply add in half precision. Do the operation in single precision. |
| // -(rs1 * rs2) - rs3 |
| void RiscVZfhFnmadd(const Instruction* instruction) { |
| RiscVZfhTernaryHelper<HalfFP, float>( |
| instruction, |
| [](float a, float b, float c) -> float { return fma(-a, b, -c); }); |
| } |
| |
| // Fused multiply add in half precision. Do the operation in single precision. |
| // -(rs1 * rs2) + rs3 |
| void RiscVZfhFnmsub(const Instruction* instruction) { |
| RiscVZfhTernaryHelper<HalfFP, float>( |
| instruction, |
| [](float a, float b, float c) -> float { return fma(-a, b, c); }); |
| } |
| |
| // Convert from signed 32 bit integer to half precision. |
| void RiscVZfhCvtHw(const Instruction* instruction) { |
| RiscVZfhCvtHelper<HalfFP, int32_t>( |
| instruction, |
| [](int32_t a, FPRoundingMode rm, uint32_t& fflags) -> HalfFP { |
| float input_float = static_cast<float>(a); |
| return ConvertToHalfFP(input_float, rm, fflags); |
| }); |
| } |
| |
| // Convert from unsigned 32 bit integer to half precision. |
| void RiscVZfhCvtHwu(const Instruction* instruction) { |
| RiscVZfhCvtHelper<HalfFP, uint32_t>( |
| instruction, |
| [](uint32_t a, FPRoundingMode rm, uint32_t& fflags) -> HalfFP { |
| float input_float = static_cast<float>(a); |
| return ConvertToHalfFP(input_float, rm, fflags); |
| }); |
| } |
| |
| // TODO(b/409778536): Factor out generic unimplemented instruction semantic |
| // function. |
| void RV32VUnimplementedInstruction(const Instruction* instruction) { |
| auto* state = static_cast<RiscVState*>(instruction->state()); |
| state->Trap(/*is_interrupt*/ false, /*trap_value*/ 0, |
| *ExceptionCode::kIllegalInstruction, |
| /*epc*/ instruction->address(), instruction); |
| } |
| |
| } // namespace riscv |
| } // namespace sim |
| } // namespace mpact |