No public description

PiperOrigin-RevId: 932550923
Change-Id: I48865f964577c2e2918acb706198934ec3e0c611
diff --git a/riscv/riscv_vector_instruction_helpers.h b/riscv/riscv_vector_instruction_helpers.h
index e26aebe..0386225 100644
--- a/riscv/riscv_vector_instruction_helpers.h
+++ b/riscv/riscv_vector_instruction_helpers.h
@@ -21,10 +21,12 @@
 #include <limits>
 #include <optional>
 #include <tuple>
+#include <type_traits>
 
 #include "absl/log/log.h"
 #include "absl/strings/str_cat.h"
 #include "mpact/sim/generic/instruction.h"
+#include "mpact/sim/generic/type_helpers.h"
 #include "riscv/riscv_register.h"
 #include "riscv/riscv_state.h"
 #include "riscv/riscv_vector_state.h"
@@ -35,6 +37,23 @@
 
 using ::mpact::sim::generic::GetInstructionSource;
 
+template <typename T>
+inline constexpr bool IsMpactFpType =
+    std::is_floating_point<T>::value ||
+    std::is_same<typename std::decay<T>::type,
+                 mpact::sim::generic::HalfFP>::value;
+
+template <typename T>
+inline T CanonicalizeVectorNaN(T value) {
+  if constexpr (IsMpactFpType<T>) {
+    if (mpact::sim::generic::FPTypeInfo<T>::IsNaN(value)) {
+      auto nan_val = mpact::sim::generic::FPTypeInfo<T>::kCanonicalNaN;
+      return *reinterpret_cast<T*>(&nan_val);
+    }
+  }
+  return value;
+}
+
 // This helper function handles the case of instructions that target a vector
 // mask.
 // It clears the masked bit and uses the mask value in the
@@ -214,7 +233,7 @@
 
       auto result = op(operation_mask);
       if (mask_value) {
-        dest_span[i] = result;
+        dest_span[i] = CanonicalizeVectorNaN<Vd>(result);
       }
       vector_index++;
     }
@@ -285,7 +304,7 @@
       if (mask_value) {
         // Compute result.
         Vs2 vs2 = GetInstructionSource<Vs2>(inst, 0, vector_index);
-        dest_span[i] = op(vs2);
+        dest_span[i] = CanonicalizeVectorNaN<Vd>(op(vs2));
       }
       vector_index++;
     }
@@ -359,7 +378,7 @@
         // Compute result.
         Vs2 vs2 = GetInstructionSource<Vs2>(inst, 0, vector_index);
         auto [value, flag] = op(vs2);
-        dest_span[i] = value;
+        dest_span[i] = CanonicalizeVectorNaN<Vd>(value);
         fflags |= flag;
       }
       vector_index++;
@@ -442,7 +461,7 @@
                                           (vector_scalar ? 0 : vector_index));
       auto value = op(vs2, vs1, mask_value);
       if (value.has_value()) {
-        dest_span[i] = value.value();
+        dest_span[i] = CanonicalizeVectorNaN<Vd>(value.value());
       } else if (mask_value) {
         // If there is no value returned, but the mask_value is true, check
         // to see if there was an exception.
@@ -543,7 +562,7 @@
                                           (vector_scalar ? 0 : vector_index));
       if (mask_value) {
         auto [value, flag] = op(vs2, vs1);
-        dest_span[i] = value;
+        dest_span[i] = CanonicalizeVectorNaN<Vd>(value);
         fflags |= flag;
         if (rv_vector->vector_exception()) {
           rv_vector->set_vstart(vector_index);
@@ -629,7 +648,7 @@
                                           (vector_scalar ? 0 : vector_index));
       Vd vd = GetInstructionSource<Vd>(inst, 2, vector_index);
       if (mask_value) {
-        dest_span[i] = op(vs2, vs1, vd);
+        dest_span[i] = CanonicalizeVectorNaN<Vd>(op(vs2, vs1, vd));
       }
       vector_index++;
     }
@@ -685,7 +704,7 @@
   auto* dest_op =
       static_cast<RV32VectorDestinationOperand*>(inst->Destination(0));
   auto dest_db = dest_op->CopyDataBuffer();
-  dest_db->Set<Vd>(0, accumulator);
+  dest_db->Set<Vd>(0, CanonicalizeVectorNaN<Vd>(accumulator));
   dest_db->Submit();
   rv_vector->clear_vstart();
 }
diff --git a/riscv/test/riscv_vector_fp_instructions_test.cc b/riscv/test/riscv_vector_fp_instructions_test.cc
index 98d1673..cd390c9 100644
--- a/riscv/test/riscv_vector_fp_instructions_test.cc
+++ b/riscv/test/riscv_vector_fp_instructions_test.cc
@@ -307,6 +307,289 @@
     }
   }
 
+  // Tests if vector arithmetic instructions properly canonicalize NaNs.
+  template <typename Vd, typename Vs2, typename Vs1>
+  void CanonicalNaNBitwiseTestHelperVV(absl::string_view name, int sew,
+                                       bool has_fflags = false) {
+    constexpr int vs2_size = kVectorLengthInBytes / sizeof(Vs2);
+    constexpr int vs1_size = kVectorLengthInBytes / sizeof(Vs1);
+    Vs2 vs2_value[vs2_size * 8];
+    Vs1 vs1_value[vs1_size * 8];
+    auto vs2_span = Span<Vs2>(vs2_value);
+    auto vs1_span = Span<Vs1>(vs1_value);
+
+    AppendVectorRegisterOperands({kVs2, kVs1, kVmask}, {kVd});
+    if (has_fflags) {
+      auto* flag_op =
+          rv_fp_->fflags()->CreateSetDestinationOperand(0, "fflags");
+      instruction_->AppendDestination(flag_op);
+    }
+    SetVectorRegisterValues<uint8_t>(
+        {{kVmaskName, Span<const uint8_t>(kA5Mask)}});
+    vreg_[kVmask]->data_buffer()->Set<uint8_t>(0, 0xff);
+
+    // Fill vs2 and vs1 elements with non-canonical NaNs
+    using Vs2Int = typename FPTypeInfo<Vs2>::IntType;
+    using Vs1Int = typename FPTypeInfo<Vs1>::IntType;
+    Vs2Int non_canonical_nan_vs2 =
+        (sizeof(Vs2) == 4) ? 0xffc00000 : 0xfff8000000000000ULL;
+    Vs1Int non_canonical_nan_vs1 =
+        (sizeof(Vs1) == 4) ? 0xffc00000 : 0xfff8000000000000ULL;
+    Vs2 non_canonical_fp_nan_vs2 =
+        *reinterpret_cast<Vs2*>(&non_canonical_nan_vs2);
+    Vs1 non_canonical_fp_nan_vs1 =
+        *reinterpret_cast<Vs1*>(&non_canonical_nan_vs1);
+
+    for (int i = 0; i < vs2_size * 8; i++)
+      vs2_span[i] = non_canonical_fp_nan_vs2;
+    for (int i = 0; i < vs1_size * 8; i++)
+      vs1_span[i] = non_canonical_fp_nan_vs1;
+
+    for (int i = 0; i < 8; i++) {
+      auto vs2_name = absl::StrCat("v", kVs2 + i);
+      auto vs1_name = absl::StrCat("v", kVs1 + i);
+      SetVectorRegisterValues<Vs2>(
+          {{vs2_name, vs2_span.subspan(vs2_size * i, vs2_size)}});
+      SetVectorRegisterValues<Vs1>(
+          {{vs1_name, vs1_span.subspan(vs1_size * i, vs1_size)}});
+    }
+
+    int byte_sew = sew / 8;
+    uint32_t vtype =
+        (kSewSettingsByByteSize[byte_sew] << 3) | kLmulSettings[3];  // LMUL = 1
+    int vlen = kVectorLengthInBytes / byte_sew;
+    ConfigureVectorUnit(vtype, vlen);
+    rv_vector_->set_vstart(0);
+
+    instruction_->Execute();
+
+    auto* dest_db = vreg_[kVd]->data_buffer();
+    using VdInt = typename FPTypeInfo<Vd>::IntType;
+    VdInt expected_bits = FPTypeInfo<Vd>::kCanonicalNaN;
+    for (int i = 0; i < 4; i++) {  // Check first 4 elements which are unmasked
+      VdInt result_bits = dest_db->Get<VdInt>(i);
+      EXPECT_EQ(result_bits, expected_bits)
+          << name << " failed to canonicalize NaN at index " << i;
+    }
+  }
+
+  template <typename Vd, typename Vs2, typename Fs1>
+  void CanonicalNaNBitwiseTestHelperVX(absl::string_view name, int sew,
+                                       bool has_fflags = false) {
+    constexpr int vs2_size = kVectorLengthInBytes / sizeof(Vs2);
+    Vs2 vs2_value[vs2_size * 8];
+    auto vs2_span = Span<Vs2>(vs2_value);
+
+    AppendVectorRegisterOperands({kVs2}, {kVd});
+    AppendRegisterOperands<RVScalarRegister>({kFs1Name}, {});
+    if (has_fflags) {
+      auto* flag_op =
+          rv_fp_->fflags()->CreateSetDestinationOperand(0, "fflags");
+      instruction_->AppendDestination(flag_op);
+    }
+    AppendVectorRegisterOperands({kVmask}, {});
+
+    SetVectorRegisterValues<uint8_t>(
+        {{kVmaskName, Span<const uint8_t>(kA5Mask)}});
+    vreg_[kVmask]->data_buffer()->Set<uint8_t>(0, 0xff);
+
+    // Fill vs2 elements with non-canonical NaNs
+    using Vs2Int = typename FPTypeInfo<Vs2>::IntType;
+    using Fs1Int = typename FPTypeInfo<Fs1>::IntType;
+    Vs2Int non_canonical_nan_vs2 =
+        (sizeof(Vs2) == 4) ? 0xffc00000 : 0xfff8000000000000ULL;
+    Fs1Int non_canonical_nan_fs1 =
+        (sizeof(Fs1) == 4) ? 0xffc00000 : 0xfff8000000000000ULL;
+    Vs2 non_canonical_fp_nan_vs2 =
+        *reinterpret_cast<Vs2*>(&non_canonical_nan_vs2);
+    Fs1 non_canonical_fp_nan_fs1 =
+        *reinterpret_cast<Fs1*>(&non_canonical_nan_fs1);
+
+    for (int i = 0; i < vs2_size * 8; i++)
+      vs2_span[i] = non_canonical_fp_nan_vs2;
+
+    for (int i = 0; i < 8; i++) {
+      auto vs2_name = absl::StrCat("v", kVs2 + i);
+      SetVectorRegisterValues<Vs2>(
+          {{vs2_name, vs2_span.subspan(vs2_size * i, vs2_size)}});
+    }
+
+    typename RVScalarRegister::ValueType fs1_reg_value =
+        NaNBox<Fs1, typename RVScalarRegister::ValueType>(
+            non_canonical_fp_nan_fs1);
+    SetRegisterValues<typename RVScalarRegister::ValueType, RVScalarRegister>(
+        {{kFs1Name, fs1_reg_value}});
+
+    int byte_sew = sew / 8;
+    uint32_t vtype =
+        (kSewSettingsByByteSize[byte_sew] << 3) | kLmulSettings[3];  // LMUL = 1
+    int vlen = kVectorLengthInBytes / byte_sew;
+    ConfigureVectorUnit(vtype, vlen);
+    rv_vector_->set_vstart(0);
+
+    instruction_->Execute();
+
+    auto* dest_db = vreg_[kVd]->data_buffer();
+    using VdInt = typename FPTypeInfo<Vd>::IntType;
+    VdInt expected_bits = FPTypeInfo<Vd>::kCanonicalNaN;
+    for (int i = 0; i < 4; i++) {  // Check first 4 elements which are unmasked
+      VdInt result_bits = dest_db->Get<VdInt>(i);
+      EXPECT_EQ(result_bits, expected_bits)
+          << name << " failed to canonicalize NaN at index " << i;
+    }
+  }
+
+  template <typename Vd, typename Vs2, typename Vs1>
+  void CanonicalNaNBitwiseTestHelperTernaryVV(absl::string_view name, int sew,
+                                              bool has_fflags = false) {
+    constexpr int vs2_size = kVectorLengthInBytes / sizeof(Vs2);
+    constexpr int vs1_size = kVectorLengthInBytes / sizeof(Vs1);
+    constexpr int vd_size = kVectorLengthInBytes / sizeof(Vd);
+    Vs2 vs2_value[vs2_size * 8];
+    Vs1 vs1_value[vs1_size * 8];
+    Vd vd_value[vd_size * 8];
+    auto vs2_span = Span<Vs2>(vs2_value);
+    auto vs1_span = Span<Vs1>(vs1_value);
+    auto vd_span = Span<Vd>(vd_value);
+
+    AppendVectorRegisterOperands({kVs2, kVs1, kVd, kVmask}, {kVd});
+    if (has_fflags) {
+      auto* flag_op =
+          rv_fp_->fflags()->CreateSetDestinationOperand(0, "fflags");
+      instruction_->AppendDestination(flag_op);
+    }
+    SetVectorRegisterValues<uint8_t>(
+        {{kVmaskName, Span<const uint8_t>(kA5Mask)}});
+    vreg_[kVmask]->data_buffer()->Set<uint8_t>(0, 0xff);
+
+    // Fill elements with non-canonical NaNs
+    using Vs2Int = typename FPTypeInfo<Vs2>::IntType;
+    using Vs1Int = typename FPTypeInfo<Vs1>::IntType;
+    using VdInt = typename FPTypeInfo<Vd>::IntType;
+    Vs2Int non_canonical_nan_vs2 =
+        (sizeof(Vs2) == 4) ? 0xffc00000 : 0xfff8000000000000ULL;
+    Vs1Int non_canonical_nan_vs1 =
+        (sizeof(Vs1) == 4) ? 0xffc00000 : 0xfff8000000000000ULL;
+    VdInt non_canonical_nan_vd =
+        (sizeof(Vd) == 4) ? 0xffc00000 : 0xfff8000000000000ULL;
+    Vs2 non_canonical_fp_nan_vs2 =
+        *reinterpret_cast<Vs2*>(&non_canonical_nan_vs2);
+    Vs1 non_canonical_fp_nan_vs1 =
+        *reinterpret_cast<Vs1*>(&non_canonical_nan_vs1);
+    Vd non_canonical_fp_nan_vd = *reinterpret_cast<Vd*>(&non_canonical_nan_vd);
+
+    for (int i = 0; i < vs2_size * 8; i++)
+      vs2_span[i] = non_canonical_fp_nan_vs2;
+    for (int i = 0; i < vs1_size * 8; i++)
+      vs1_span[i] = non_canonical_fp_nan_vs1;
+    for (int i = 0; i < vd_size * 8; i++) vd_span[i] = non_canonical_fp_nan_vd;
+
+    for (int i = 0; i < 8; i++) {
+      auto vs2_name = absl::StrCat("v", kVs2 + i);
+      auto vs1_name = absl::StrCat("v", kVs1 + i);
+      auto vd_name = absl::StrCat("v", kVd + i);
+      SetVectorRegisterValues<Vs2>(
+          {{vs2_name, vs2_span.subspan(vs2_size * i, vs2_size)}});
+      SetVectorRegisterValues<Vs1>(
+          {{vs1_name, vs1_span.subspan(vs1_size * i, vs1_size)}});
+      SetVectorRegisterValues<Vd>(
+          {{vd_name, vd_span.subspan(vd_size * i, vd_size)}});
+    }
+
+    int byte_sew = sew / 8;
+    uint32_t vtype =
+        (kSewSettingsByByteSize[byte_sew] << 3) | kLmulSettings[3];  // LMUL = 1
+    int vlen = kVectorLengthInBytes / byte_sew;
+    ConfigureVectorUnit(vtype, vlen);
+    rv_vector_->set_vstart(0);
+
+    instruction_->Execute();
+
+    auto* dest_db = vreg_[kVd]->data_buffer();
+    VdInt expected_bits = FPTypeInfo<Vd>::kCanonicalNaN;
+    for (int i = 0; i < 4; i++) {  // Check first 4 elements which are unmasked
+      VdInt result_bits = dest_db->Get<VdInt>(i);
+      EXPECT_EQ(result_bits, expected_bits)
+          << name << " failed to canonicalize NaN at index " << i;
+    }
+  }
+
+  template <typename Vd, typename Vs2, typename Fs1>
+  void CanonicalNaNBitwiseTestHelperTernaryVX(absl::string_view name, int sew,
+                                              bool has_fflags = false) {
+    constexpr int vs2_size = kVectorLengthInBytes / sizeof(Vs2);
+    constexpr int vd_size = kVectorLengthInBytes / sizeof(Vd);
+    Vs2 vs2_value[vs2_size * 8];
+    Vd vd_value[vd_size * 8];
+    auto vs2_span = Span<Vs2>(vs2_value);
+    auto vd_span = Span<Vd>(vd_value);
+
+    AppendVectorRegisterOperands({kVs2}, {kVd});
+    AppendRegisterOperands<RVScalarRegister>({kFs1Name}, {});
+    AppendVectorRegisterOperands({kVd, kVmask}, {kVd});
+    if (has_fflags) {
+      auto* flag_op =
+          rv_fp_->fflags()->CreateSetDestinationOperand(0, "fflags");
+      instruction_->AppendDestination(flag_op);
+    }
+
+    SetVectorRegisterValues<uint8_t>(
+        {{kVmaskName, Span<const uint8_t>(kA5Mask)}});
+    vreg_[kVmask]->data_buffer()->Set<uint8_t>(0, 0xff);
+
+    // Fill elements with non-canonical NaNs
+    using Vs2Int = typename FPTypeInfo<Vs2>::IntType;
+    using Fs1Int = typename FPTypeInfo<Fs1>::IntType;
+    using VdInt = typename FPTypeInfo<Vd>::IntType;
+    Vs2Int non_canonical_nan_vs2 =
+        (sizeof(Vs2) == 4) ? 0xffc00000 : 0xfff8000000000000ULL;
+    Fs1Int non_canonical_nan_fs1 =
+        (sizeof(Fs1) == 4) ? 0xffc00000 : 0xfff8000000000000ULL;
+    VdInt non_canonical_nan_vd =
+        (sizeof(Vd) == 4) ? 0xffc00000 : 0xfff8000000000000ULL;
+    Vs2 non_canonical_fp_nan_vs2 =
+        *reinterpret_cast<Vs2*>(&non_canonical_nan_vs2);
+    Fs1 non_canonical_fp_nan_fs1 =
+        *reinterpret_cast<Fs1*>(&non_canonical_nan_fs1);
+    Vd non_canonical_fp_nan_vd = *reinterpret_cast<Vd*>(&non_canonical_nan_vd);
+
+    for (int i = 0; i < vs2_size * 8; i++)
+      vs2_span[i] = non_canonical_fp_nan_vs2;
+    for (int i = 0; i < vd_size * 8; i++) vd_span[i] = non_canonical_fp_nan_vd;
+
+    for (int i = 0; i < 8; i++) {
+      auto vs2_name = absl::StrCat("v", kVs2 + i);
+      auto vd_name = absl::StrCat("v", kVd + i);
+      SetVectorRegisterValues<Vs2>(
+          {{vs2_name, vs2_span.subspan(vs2_size * i, vs2_size)}});
+      SetVectorRegisterValues<Vd>(
+          {{vd_name, vd_span.subspan(vd_size * i, vd_size)}});
+    }
+
+    typename RVScalarRegister::ValueType fs1_reg_value =
+        NaNBox<Fs1, typename RVScalarRegister::ValueType>(
+            non_canonical_fp_nan_fs1);
+    SetRegisterValues<typename RVScalarRegister::ValueType, RVScalarRegister>(
+        {{kFs1Name, fs1_reg_value}});
+
+    int byte_sew = sew / 8;
+    uint32_t vtype =
+        (kSewSettingsByByteSize[byte_sew] << 3) | kLmulSettings[3];  // LMUL = 1
+    int vlen = kVectorLengthInBytes / byte_sew;
+    ConfigureVectorUnit(vtype, vlen);
+    rv_vector_->set_vstart(0);
+
+    instruction_->Execute();
+
+    auto* dest_db = vreg_[kVd]->data_buffer();
+    VdInt expected_bits = FPTypeInfo<Vd>::kCanonicalNaN;
+    for (int i = 0; i < 4; i++) {  // Check first 4 elements which are unmasked
+      VdInt result_bits = dest_db->Get<VdInt>(i);
+      EXPECT_EQ(result_bits, expected_bits)
+          << name << " failed to canonicalize NaN at index " << i;
+    }
+  }
+
   // Floating point test needs to ensure to use the fp special values (inf, NaN
   // etc.) during testing, not just random values. This function handles vector
   // scalar instructions.
@@ -505,6 +788,12 @@
 TEST_F(RiscVFPInstructionsTest, Vfadd) {
   // Vector-vector.
   SetSemanticFunction(&Vfadd);
+  CanonicalNaNBitwiseTestHelperVV<float, float, float>("Vfadd_vv32_nan", 32);
+  ResetInstruction();
+  SetSemanticFunction(&Vfadd);
+  CanonicalNaNBitwiseTestHelperVV<double, double, double>("Vfadd_vv64_nan", 64);
+  ResetInstruction();
+  SetSemanticFunction(&Vfadd);
   BinaryOpFPTestHelperVV<float, float, float>(
       "Vfadd_vv32", /*sew*/ 32, instruction_, /*delta_position*/ 32,
       [](float vs2, float vs1) -> float { return vs2 + vs1; });
@@ -516,6 +805,12 @@
   // Vector-scalar.
   ResetInstruction();
   SetSemanticFunction(&Vfadd);
+  CanonicalNaNBitwiseTestHelperVX<float, float, float>("Vfadd_vx32_nan", 32);
+  ResetInstruction();
+  SetSemanticFunction(&Vfadd);
+  CanonicalNaNBitwiseTestHelperVX<double, double, double>("Vfadd_vx64_nan", 64);
+  ResetInstruction();
+  SetSemanticFunction(&Vfadd);
   BinaryOpFPTestHelperVX<float, float, float, RVScalarRegister>(
       "Vfadd_vx32", /*sew*/ 32, instruction_, /*delta_position*/ 32,
       [](float vs2, float vs1) -> float { return vs2 + vs1; });
@@ -882,6 +1177,14 @@
 TEST_F(RiscVFPInstructionsTest, Vfmacc) {
   // Vector-vector.
   SetSemanticFunction(&Vfmacc);
+  CanonicalNaNBitwiseTestHelperTernaryVV<float, float, float>("Vfmacc_vv32_nan",
+                                                              32);
+  ResetInstruction();
+  SetSemanticFunction(&Vfmacc);
+  CanonicalNaNBitwiseTestHelperTernaryVV<double, double, double>(
+      "Vfmacc_vv64_nan", 64);
+  ResetInstruction();
+  SetSemanticFunction(&Vfmacc);
   TernaryOpFPTestHelperVV<float, float, float>(
       "Vfmacc_vv32", /*sew*/ 32, instruction_, /*delta_position*/ 32,
       [](float vs2, float vs1, float vd) -> float {
@@ -897,6 +1200,14 @@
   // Vector-scalar.
   ResetInstruction();
   SetSemanticFunction(&Vfmacc);
+  CanonicalNaNBitwiseTestHelperTernaryVX<float, float, float>("Vfmacc_vx32_nan",
+                                                              32);
+  ResetInstruction();
+  SetSemanticFunction(&Vfmacc);
+  CanonicalNaNBitwiseTestHelperTernaryVX<double, double, double>(
+      "Vfmacc_vx64_nan", 64);
+  ResetInstruction();
+  SetSemanticFunction(&Vfmacc);
   TernaryOpFPTestHelperVX<float, float, float>(
       "Vfmacc_vx32", /*sew*/ 32, instruction_, /*delta_position*/ 32,
       [](float vs2, float vs1, float vd) -> float {
@@ -1291,6 +1602,14 @@
 TEST_F(RiscVFPInstructionsTest, Vfmax) {
   // Vector-vector.
   SetSemanticFunction(&Vfmax);
+  CanonicalNaNBitwiseTestHelperVV<float, float, float>("Vfmax_vv32_nan", 32,
+                                                       true);
+  ResetInstruction();
+  SetSemanticFunction(&Vfmax);
+  CanonicalNaNBitwiseTestHelperVV<double, double, double>("Vfmax_vv64_nan", 64,
+                                                          true);
+  ResetInstruction();
+  SetSemanticFunction(&Vfmax);
   BinaryOpWithFflagsFPTestHelperVV<float, float, float>(
       "Vfmax_vv32", /*sew*/ 32, instruction_, /*delta_position*/ 32,
       [](float vs2, float vs1) -> std::tuple<float, uint32_t> {
@@ -1313,6 +1632,14 @@
   // Vector-scalar.
   ResetInstruction();
   SetSemanticFunction(&Vfmax);
+  CanonicalNaNBitwiseTestHelperVX<float, float, float>("Vfmax_vx32_nan", 32,
+                                                       true);
+  ResetInstruction();
+  SetSemanticFunction(&Vfmax);
+  CanonicalNaNBitwiseTestHelperVX<double, double, double>("Vfmax_vx64_nan", 64,
+                                                          true);
+  ResetInstruction();
+  SetSemanticFunction(&Vfmax);
   BinaryOpWithFflagsFPTestHelperVX<float, float, float, RVScalarRegister>(
       "Vfmax_vx32", /*sew*/ 32, instruction_, /*delta_position*/ 32,
       [](float vs2, float vs1) -> std::tuple<float, uint32_t> {
@@ -1392,4 +1719,55 @@
       });
 }
 
+TEST_F(RiscVFPInstructionsTest, VfrdivCanonicalNaNBitwise) {
+  SetSemanticFunction(&Vfrdiv);
+
+  constexpr int vs2_size = kVectorLengthInBytes / sizeof(float);
+  float vs2_value[vs2_size * 8];
+  auto vs2_span = Span<float>(vs2_value);
+
+  AppendVectorRegisterOperands({kVs2}, {kVd});
+  AppendRegisterOperands<RVScalarRegister>({kFs1Name}, {});
+  AppendVectorRegisterOperands({kVmask}, {});
+
+  SetVectorRegisterValues<uint8_t>(
+      {{kVmaskName, Span<const uint8_t>(kA5Mask)}});
+  vreg_[kVmask]->data_buffer()->Set<uint8_t>(0, 0xff);
+
+  // Fill vs2 elements with non-canonical float single-precision quiet NaN
+  // payload bits (0xffc00000)
+  uint32_t non_canonical_nan = 0xffc00000;
+  float non_canonical_float_nan = *reinterpret_cast<float*>(&non_canonical_nan);
+  for (int i = 0; i < vs2_size * 8; i++) {
+    vs2_span[i] = non_canonical_float_nan;
+  }
+
+  for (int i = 0; i < 8; i++) {
+    auto vs2_name = absl::StrCat("v", kVs2 + i);
+    SetVectorRegisterValues<float>(
+        {{vs2_name, vs2_span.subspan(vs2_size * i, vs2_size)}});
+  }
+
+  // Set scalar operand fs1 value
+  float fs1_val = 1.0f;
+  typename RVScalarRegister::ValueType fs1_reg_value =
+      NaNBox<float, typename RVScalarRegister::ValueType>(fs1_val);
+  SetRegisterValues<typename RVScalarRegister::ValueType, RVScalarRegister>(
+      {{kFs1Name, fs1_reg_value}});
+
+  // sew = 32 bits = 4 bytes, sew_field = 2
+  uint32_t vtype =
+      (kSewSettingsByByteSize[4] << 3) | kLmulSettings[3];  // LMUL = 1
+  ConfigureVectorUnit(vtype, 4);
+  rv_vector_->set_vstart(0);
+
+  instruction_->Execute();
+
+  auto* dest_db = vreg_[kVd]->data_buffer();
+  for (int i = 0; i < 4; i++) {
+    uint32_t result_bits = dest_db->Get<uint32_t>(i);
+    EXPECT_EQ(result_bits, 0x7fc00000);
+  }
+}
+
 }  // namespace
diff --git a/riscv/test/riscv_vector_fp_reduction_instructions_test.cc b/riscv/test/riscv_vector_fp_reduction_instructions_test.cc
index bd7535e..355ad22 100644
--- a/riscv/test/riscv_vector_fp_reduction_instructions_test.cc
+++ b/riscv/test/riscv_vector_fp_reduction_instructions_test.cc
@@ -65,6 +65,65 @@
 class RiscVFPReductionInstructionsTest
     : public ::mpact::sim::riscv::test::RiscVFPInstructionsTestBase {
  public:
+  // Tests if vector arithmetic instructions properly canonicalize NaNs.
+  template <typename Vd, typename Vs2, typename Vs1>
+  void CanonicalNaNBitwiseTestHelperReduction(absl::string_view name, int sew) {
+    int byte_sew = sew / 8;
+    constexpr int vs2_size = kVectorLengthInBytes / sizeof(Vs2);
+    constexpr int vs1_size = kVectorLengthInBytes / sizeof(Vs1);
+    Vs2 vs2_value[vs2_size * 8];
+    auto vs2_span = Span<Vs2>(vs2_value);
+    Vs1 vs1_value[vs1_size * 8];
+    auto vs1_span = Span<Vs1>(vs1_value);
+
+    AppendVectorRegisterOperands({kVs2, kVs1, kVmask}, {kVd});
+    auto mask_span = Span<const uint8_t>(kA5Mask);
+    SetVectorRegisterValues<uint8_t>({{kVmaskName, mask_span}});
+
+    // Fill elements with non-canonical NaNs
+    using Vs2Int = typename FPTypeInfo<Vs2>::IntType;
+    using Vs1Int = typename FPTypeInfo<Vs1>::IntType;
+    Vs2Int non_canonical_nan_vs2 =
+        (sizeof(Vs2) == 4) ? 0xffc00000 : 0xfff8000000000000ULL;
+    Vs1Int non_canonical_nan_vs1 =
+        (sizeof(Vs1) == 4) ? 0xffc00000 : 0xfff8000000000000ULL;
+    Vs2 non_canonical_fp_nan_vs2 =
+        *reinterpret_cast<Vs2*>(&non_canonical_nan_vs2);
+    Vs1 non_canonical_fp_nan_vs1 =
+        *reinterpret_cast<Vs1*>(&non_canonical_nan_vs1);
+
+    for (int i = 0; i < vs2_size * 8; i++)
+      vs2_span[i] = non_canonical_fp_nan_vs2;
+    for (int i = 0; i < vs1_size * 8; i++)
+      vs1_span[i] = non_canonical_fp_nan_vs1;
+
+    for (int i = 0; i < 8; i++) {
+      auto vs2_name = absl::StrCat("v", kVs2 + i);
+      auto vs1_name = absl::StrCat("v", kVs1 + i);
+      SetVectorRegisterValues<Vs2>(
+          {{vs2_name, vs2_span.subspan(vs2_size * i, vs2_size)}});
+      SetVectorRegisterValues<Vs1>(
+          {{vs1_name, vs1_span.subspan(vs1_size * i, vs1_size)}});
+    }
+
+    uint32_t vtype =
+        (kSewSettingsByByteSize[byte_sew] << 3) | kLmulSettings[3];  // LMUL = 1
+    int vlen = kVectorLengthInBytes / byte_sew;
+    ConfigureVectorUnit(vtype, vlen);
+
+    rv_fp_->SetRoundingMode(static_cast<FPRoundingMode>(0));
+    ClearVectorRegisterGroup(kVd, 8);
+
+    instruction_->Execute();
+
+    auto reg_val = vreg_[kVd]->data_buffer()->Get<Vd>(0);
+    using VdInt = typename FPTypeInfo<Vd>::IntType;
+    VdInt result_bits = *reinterpret_cast<VdInt*>(&reg_val);
+    VdInt expected_bits = FPTypeInfo<Vd>::kCanonicalNaN;
+    EXPECT_EQ(result_bits, expected_bits)
+        << name << " failed to canonicalize NaN in reduction";
+  }
+
   // Helper function for floating point reduction operations.
   template <typename Vd, typename Vs2, typename Vs1>
   void ReductionOpFPTestHelper(absl::string_view name, int sew,
@@ -171,6 +230,14 @@
 // Test vector floating point sum reduction.
 TEST_F(RiscVFPReductionInstructionsTest, Vfredosum) {
   SetSemanticFunction(&Vfredosum);
+  CanonicalNaNBitwiseTestHelperReduction<float, float, float>(
+      "Vfredosum_32_nan", 32);
+  ResetInstruction();
+  SetSemanticFunction(&Vfredosum);
+  CanonicalNaNBitwiseTestHelperReduction<double, double, double>(
+      "Vfredosum_64_nan", 64);
+  ResetInstruction();
+  SetSemanticFunction(&Vfredosum);
   ReductionOpFPTestHelper<float, float, float>(
       "Vfredosum_32", /*sew*/ 32, instruction_, /*delta_position*/ 32,
       [](float val0, float val1) -> float { return val0 + val1; });
@@ -215,6 +282,14 @@
 // Test vector floating point min reduction.
 TEST_F(RiscVFPReductionInstructionsTest, Vfredmin) {
   SetSemanticFunction(&Vfredmin);
+  CanonicalNaNBitwiseTestHelperReduction<float, float, float>("Vfredmin_32_nan",
+                                                              32);
+  ResetInstruction();
+  SetSemanticFunction(&Vfredmin);
+  CanonicalNaNBitwiseTestHelperReduction<double, double, double>(
+      "Vfredmin_64_nan", 64);
+  ResetInstruction();
+  SetSemanticFunction(&Vfredmin);
   ReductionOpFPTestHelper<float, float, float>(
       "Vfredmin_32", /*sew*/ 32, instruction_, /*delta_position*/ 32,
       [](float val0, float val1) -> float {
@@ -238,6 +313,14 @@
 // Test vector floating point max reduction.
 TEST_F(RiscVFPReductionInstructionsTest, Vfredmax) {
   SetSemanticFunction(&Vfredmax);
+  CanonicalNaNBitwiseTestHelperReduction<float, float, float>("Vfredmax_32_nan",
+                                                              32);
+  ResetInstruction();
+  SetSemanticFunction(&Vfredmax);
+  CanonicalNaNBitwiseTestHelperReduction<double, double, double>(
+      "Vfredmax_64_nan", 64);
+  ResetInstruction();
+  SetSemanticFunction(&Vfredmax);
   ReductionOpFPTestHelper<float, float, float>(
       "Vfredmin_32", /*sew*/ 32, instruction_, /*delta_position*/ 32,
       [](float val0, float val1) -> float {
diff --git a/riscv/test/riscv_vector_fp_unary_instructions_test.cc b/riscv/test/riscv_vector_fp_unary_instructions_test.cc
index 04540fe..a15e57b 100644
--- a/riscv/test/riscv_vector_fp_unary_instructions_test.cc
+++ b/riscv/test/riscv_vector_fp_unary_instructions_test.cc
@@ -269,6 +269,59 @@
     }
   }
 
+  // Tests if unary fp vector instructions properly canonicalize NaNs.
+  template <typename Vd, typename Vs2>
+  void CanonicalNaNBitwiseTestHelperUnaryV(absl::string_view name, int sew,
+                                           bool has_fflags = false) {
+    constexpr int vs2_size = kVectorLengthInBytes / sizeof(Vs2);
+    Vs2 vs2_value[vs2_size * 8];
+    auto vs2_span = Span<Vs2>(vs2_value);
+
+    AppendVectorRegisterOperands({kVs2, kVmask}, {kVd});
+    if (has_fflags) {
+      auto* flag_op =
+          rv_fp_->fflags()->CreateSetDestinationOperand(0, "fflags");
+      instruction_->AppendDestination(flag_op);
+    }
+    SetVectorRegisterValues<uint8_t>(
+        {{kVmaskName, Span<const uint8_t>(kA5Mask)}});
+    vreg_[kVmask]->data_buffer()->Set<uint8_t>(0, 0xff);
+
+    // Fill elements with non-canonical NaNs
+    using Vs2Int = typename FPTypeInfo<Vs2>::IntType;
+    Vs2Int non_canonical_nan_vs2 =
+        (sizeof(Vs2) == 4) ? 0xffc00000 : 0xfff8000000000000ULL;
+    Vs2 non_canonical_fp_nan_vs2 =
+        *reinterpret_cast<Vs2*>(&non_canonical_nan_vs2);
+
+    for (int i = 0; i < vs2_size * 8; i++)
+      vs2_span[i] = non_canonical_fp_nan_vs2;
+
+    for (int i = 0; i < 8; i++) {
+      auto vs2_name = absl::StrCat("v", kVs2 + i);
+      SetVectorRegisterValues<Vs2>(
+          {{vs2_name, vs2_span.subspan(vs2_size * i, vs2_size)}});
+    }
+
+    int byte_sew = sew / 8;
+    uint32_t vtype =
+        (kSewSettingsByByteSize[byte_sew] << 3) | kLmulSettings[3];  // LMUL = 1
+    int vlen = kVectorLengthInBytes / byte_sew;
+    ConfigureVectorUnit(vtype, vlen);
+    rv_vector_->set_vstart(0);
+
+    instruction_->Execute();
+
+    auto* dest_db = vreg_[kVd]->data_buffer();
+    using VdInt = typename FPTypeInfo<Vd>::IntType;
+    VdInt expected_bits = FPTypeInfo<Vd>::kCanonicalNaN;
+    for (int i = 0; i < 4; i++) {  // Check first 4 elements which are unmasked
+      VdInt result_bits = dest_db->Get<VdInt>(i);
+      EXPECT_EQ(result_bits, expected_bits)
+          << name << " failed to canonicalize NaN at index " << i;
+    }
+  }
+
   // Floating point test needs to ensure to use the fp special values (inf, NaN
   // etc.) during testing, not just random values.
   template <typename Vd, typename Vs2>
@@ -921,6 +974,14 @@
 // Test square root instruction.
 TEST_F(RiscVFPUnaryInstructionsTest, Vfsqrtv) {
   SetSemanticFunction(&Vfsqrtv);
+  CanonicalNaNBitwiseTestHelperUnaryV<float, float>("Vfsqrt.v_32_nan", 32,
+                                                    true);
+  ResetInstruction();
+  SetSemanticFunction(&Vfsqrtv);
+  CanonicalNaNBitwiseTestHelperUnaryV<double, double>("Vfsqrt.v_64_nan", 64,
+                                                      true);
+  ResetInstruction();
+  SetSemanticFunction(&Vfsqrtv);
   UnaryOpWithFflagsFPTestHelperV<float, float>(
       "Vfsqrt.v_32", /*sew*/ 32, instruction_,
       /*delta_position*/ 32, [](float vs2) -> std::tuple<float, uint32_t> {