zfh: fmin, fmax, fsgn semantic functions PiperOrigin-RevId: 755956117 Change-Id: I4d8eaf54f63efb1f79d8861e30879a15f1b3075d
diff --git a/riscv/riscv_zfh.isa b/riscv/riscv_zfh.isa index f4461f7..11e9a89 100644 --- a/riscv/riscv_zfh.isa +++ b/riscv/riscv_zfh.isa
@@ -136,5 +136,25 @@ resources: {next_pc, frs1, frs2 : frd[0..]}, semfunc: "&RiscVZfhFdiv", disasm: "fdiv.h", "%frd, %frs1, %frs2"; + fmin_h{: frs1, frs2, rm : frd, fflags}, + resources: {next_pc, frs1, frs2 : frd[0..]}, + semfunc: "&RiscVZfhFmin", + disasm: "fmin.h", "%frd, %frs1, %frs2"; + fmax_h{: frs1, frs2, rm : frd, fflags}, + resources: {next_pc, frs1, frs2 : frd[0..]}, + semfunc: "&RiscVZfhFmax", + disasm: "fmax.h", "%frd, %frs1, %frs2"; + fsgnj_h{: frs1, frs2 : frd }, + resources: {next_pc, frs1, frs2 : frd[0..]}, + semfunc: "&RiscVZfhFsgnj", + disasm: "fsgnj.h", "%frd, %frs1, %frs2"; + fsgnjn_h{: frs1, frs2 : frd }, + resources: {next_pc, frs1, frs2 : frd[0..]}, + semfunc: "&RiscVZfhFsgnjn", + disasm: "fsgnjn.h", "%frd, %frs1, %frs2"; + fsgnjnx_h{: frs1, frs2 : frd }, + resources: {next_pc, frs1, frs2 : frd[0..]}, + semfunc: "&RiscVZfhFsgnjx", + disasm: "fsgnjnx.h", "%frd, %frs1, %frs2"; } }
diff --git a/riscv/riscv_zfh_instructions.cc b/riscv/riscv_zfh_instructions.cc index 83e2342..4391530 100644 --- a/riscv/riscv_zfh_instructions.cc +++ b/riscv/riscv_zfh_instructions.cc
@@ -402,6 +402,78 @@ 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); + }); +} + +// 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))}; + }); +} + // TODO(b/409778536): Factor out generic unimplemented instruction semantic // function. void RV32VUnimplementedInstruction(const Instruction *instruction) {
diff --git a/riscv/riscv_zfh_instructions.h b/riscv/riscv_zfh_instructions.h index fd2f713..27a717d 100644 --- a/riscv/riscv_zfh_instructions.h +++ b/riscv/riscv_zfh_instructions.h
@@ -134,6 +134,45 @@ // fflags: Accrued Exception Flags field in FCSR void RiscVZfhFdiv(const Instruction *instruction); +// Source Operands: +// frs1: Float Register +// frs2: Float Register +// rm: Literal Operand (rounding mode) +// Destination Operands: +// frd: Float Register +// fflags: Accrued Exception Flags field in FCSR +void RiscVZfhFmin(const Instruction *instruction); + +// Source Operands: +// frs1: Float Register +// frs2: Float Register +// rm: Literal Operand (rounding mode) +// Destination Operands: +// frd: Float Register +// fflags: Accrued Exception Flags field in FCSR +void RiscVZfhFmax(const Instruction *instruction); + +// Source Operands: +// frs1: Float Register +// frs2: Float Register +// Destination Operands: +// frd: Float Register +void RiscVZfhFsgnj(const Instruction *instruction); + +// Source Operands: +// frs1: Float Register +// frs2: Float Register +// Destination Operands: +// frd: Float Register +void RiscVZfhFsgnjn(const Instruction *instruction); + +// Source Operands: +// frs1: Float Register +// frs2: Float Register +// Destination Operands: +// frd: Float Register +void RiscVZfhFsgnjx(const Instruction *instruction); + } // namespace riscv } // namespace sim } // namespace mpact
diff --git a/riscv/test/riscv_zfh_instructions_test.cc b/riscv/test/riscv_zfh_instructions_test.cc index 876c5f5..7f8aa01 100644 --- a/riscv/test/riscv_zfh_instructions_test.cc +++ b/riscv/test/riscv_zfh_instructions_test.cc
@@ -57,8 +57,13 @@ using ::mpact::sim::riscv::RiscVZfhFadd; using ::mpact::sim::riscv::RiscVZfhFdiv; using ::mpact::sim::riscv::RiscVZfhFlhChild; +using ::mpact::sim::riscv::RiscVZfhFmax; +using ::mpact::sim::riscv::RiscVZfhFmin; using ::mpact::sim::riscv::RiscVZfhFmul; using ::mpact::sim::riscv::RiscVZfhFMvhx; +using ::mpact::sim::riscv::RiscVZfhFsgnj; +using ::mpact::sim::riscv::RiscVZfhFsgnjn; +using ::mpact::sim::riscv::RiscVZfhFsgnjx; using ::mpact::sim::riscv::RiscVZfhFsub; using ::mpact::sim::riscv::RV32Register; using ::mpact::sim::riscv::RV64Register; @@ -682,6 +687,128 @@ }); } +// Find the minimum of two half precision values. Generate the expected result +// by using a natively supported float datatype for the operation. +TEST_F(RV32ZfhInstructionTest, RiscVZfhFmin) { + SetSemanticFunction(&RiscVZfhFmin); + BinaryOpWithFflagsFPTestHelper<HalfFP, HalfFP, HalfFP>( + "fmin.h", instruction_, {"f", "f", "f"}, 32, + [this](HalfFP a, HalfFP b) -> std::tuple<HalfFP, uint32_t> { + FPRoundingMode rm = rv_fp_->GetRoundingMode(); + uint32_t fflags = 0; + double a_f = + FpConversionsTestHelper(a).ConvertWithFlags<double>(fflags); + double b_f = + FpConversionsTestHelper(b).ConvertWithFlags<double>(fflags); + double min_f = 0; + if (a_f == 0 && b_f == 0 && (a.value != b.value)) { + // Special case for -0 vs +0. + min_f = absl::bit_cast<double>(FPTypeInfo<double>::kNegZero); + } else if (std::isnan(a_f) && !std::isnan(b_f)) { + min_f = b_f; // pick the non-NaN value + } else if (!std::isnan(a_f) && std::isnan(b_f)) { + min_f = a_f; // pick the non-NaN value + } else if (std::isnan(a_f) && std::isnan(b_f)) { + min_f = absl::bit_cast<double>(FPTypeInfo<double>::kCanonicalNaN); + } else if (std::isinf(a_f) && !std::isinf(b_f)) { + min_f = a_f < 0 ? a_f : b_f; // min(+/-inf, x) + } else if (!std::isinf(a_f) && std::isinf(b_f)) { + min_f = b_f < 0 ? b_f : a_f; // min(x, +/-inf) + } else { + if (a_f < b_f) { + min_f = a_f; + } else { + min_f = b_f; + } + } + HalfFP result = + FpConversionsTestHelper(min_f).ConvertWithFlags<HalfFP>(fflags, rm); + return std::make_tuple(result, fflags); + }); +} + +// Find the maximum of two half precision values. Generate the expected result +// by using a natively supported float datatype for the operation. +TEST_F(RV32ZfhInstructionTest, RiscVZfhFmax) { + SetSemanticFunction(&RiscVZfhFmax); + BinaryOpWithFflagsFPTestHelper<HalfFP, HalfFP, HalfFP>( + "fmax.h", instruction_, {"f", "f", "f"}, 32, + [this](HalfFP a, HalfFP b) -> std::tuple<HalfFP, uint32_t> { + FPRoundingMode rm = rv_fp_->GetRoundingMode(); + uint32_t fflags = 0; + double a_f = + FpConversionsTestHelper(a).ConvertWithFlags<double>(fflags); + double b_f = + FpConversionsTestHelper(b).ConvertWithFlags<double>(fflags); + double max_f = 0; + if (a_f == 0 && b_f == 0 && (a.value != b.value)) { + // Special case for -0 vs +0. + max_f = absl::bit_cast<double>(FPTypeInfo<double>::kPosZero); + } else if (std::isnan(a_f) && !std::isnan(b_f)) { + max_f = b_f; // pick the non-NaN value + } else if (!std::isnan(a_f) && std::isnan(b_f)) { + max_f = a_f; // pick the non-NaN value + } else if (std::isnan(a_f) && std::isnan(b_f)) { + max_f = absl::bit_cast<double>(FPTypeInfo<double>::kCanonicalNaN); + } else if (std::isinf(a_f) && !std::isinf(b_f)) { + max_f = a_f > 0 ? a_f : b_f; // max(+/-inf, x) + } else if (!std::isinf(a_f) && std::isinf(b_f)) { + max_f = b_f > 0 ? b_f : a_f; // max(x, +/-inf) + } else { + if (a_f > b_f) { + max_f = a_f; + } else { + max_f = b_f; + } + } + HalfFP result = + FpConversionsTestHelper(max_f).ConvertWithFlags<HalfFP>(fflags, rm); + return std::make_tuple(result, fflags); + }); +} + +TEST_F(RV32ZfhInstructionTest, RiscVZfhFsgnj) { + SetSemanticFunction(&RiscVZfhFsgnj); + BinaryOpWithFflagsFPTestHelper<HalfFP, HalfFP, HalfFP>( + "fsgnj.h", instruction_, {"f", "f", "f"}, 32, + [](HalfFP a, HalfFP b) -> std::tuple<HalfFP, uint32_t> { + HalfFP result{.value = 0}; + result.value |= a.value & (FPTypeInfo<HalfFP>::kExpMask | + FPTypeInfo<HalfFP>::kSigMask); + result.value |= b.value & ~(FPTypeInfo<HalfFP>::kExpMask | + FPTypeInfo<HalfFP>::kSigMask); + return std::make_tuple(result, 0); + }); +} + +TEST_F(RV32ZfhInstructionTest, RiscVZfhFsgnjn) { + SetSemanticFunction(&RiscVZfhFsgnjn); + BinaryOpWithFflagsFPTestHelper<HalfFP, HalfFP, HalfFP>( + "fsgnjn.h", instruction_, {"f", "f", "f"}, 32, + [](HalfFP a, HalfFP b) -> std::tuple<HalfFP, uint32_t> { + HalfFP result{.value = 0}; + result.value |= a.value & (FPTypeInfo<HalfFP>::kExpMask | + FPTypeInfo<HalfFP>::kSigMask); + result.value |= (~b.value) & ~(FPTypeInfo<HalfFP>::kExpMask | + FPTypeInfo<HalfFP>::kSigMask); + return std::make_tuple(result, 0); + }); +} + +TEST_F(RV32ZfhInstructionTest, RiscVZfhFsgnjx) { + SetSemanticFunction(&RiscVZfhFsgnjx); + BinaryOpWithFflagsFPTestHelper<HalfFP, HalfFP, HalfFP>( + "fsgnjn.h", instruction_, {"f", "f", "f"}, 32, + [](HalfFP a, HalfFP b) -> std::tuple<HalfFP, uint32_t> { + HalfFP result{.value = 0}; + result.value |= a.value & (FPTypeInfo<HalfFP>::kExpMask | + FPTypeInfo<HalfFP>::kSigMask); + result.value |= (a.value ^ b.value) & ~(FPTypeInfo<HalfFP>::kExpMask | + FPTypeInfo<HalfFP>::kSigMask); + return std::make_tuple(result, 0); + }); +} + class RV64ZfhInstructionTest : public RVZfhInstructionTestBase {}; // Move half precision from a float register to an integer register. The IEEE754