zfh: add nan boxing to fmv.h.x and properly handle 32 & 64 bit XLEN

PiperOrigin-RevId: 759377568
Change-Id: I6dd3146a3e8c394878d5339d6c8ce1a4cf837c81
diff --git a/riscv/riscv_zfh.isa b/riscv/riscv_zfh.isa
index 33d15cc..a6ecf23 100644
--- a/riscv/riscv_zfh.isa
+++ b/riscv/riscv_zfh.isa
@@ -35,10 +35,6 @@
     disasm: "Unimplemented instruction at 0x%(@:08x)",
     semfunc: "&RV32VUnimplementedInstruction";
   opcodes {
-    fmv_hx{: rs1 : frd},
-      resources: {next_pc, rs1 : frd[0..]},
-      semfunc: "&RiscVZfhFMvhx",
-      disasm: "fmv.h.x", "%frd, %rs1";
     fcvt_sh{: frs1, rm : frd, fflags},
       resources: {next_pc, frs1 : frd[0..]},
       semfunc: "&RiscVZfhCvtSh",
@@ -81,6 +77,10 @@
       resources: {next_pc, frs1 : rd[0..]},
       semfunc: "&RV32::RiscVZfhFMvxh",
       disasm: "fmv.x.h", "%rd, %frs1";
+    fmv_hx{: rs1 : frd},
+      resources: {next_pc, rs1 : frd[0..]},
+      semfunc: "&RV32::RiscVZfhFMvhx",
+      disasm: "fmv.h.x", "%frd, %rs1";
   }
 }
 
@@ -107,6 +107,10 @@
       resources: {next_pc, frs1 : rd[0..]},
       semfunc: "&RV64::RiscVZfhFMvxh",
       disasm: "fmv.x.h", "%rd, %frs1";
+    fmv_hx{: rs1 : frd},
+      resources: {next_pc, rs1 : frd[0..]},
+      semfunc: "&RV64::RiscVZfhFMvhx",
+      disasm: "fmv.h.x", "%frd, %rs1";
   }
 }
 
diff --git a/riscv/riscv_zfh_instructions.cc b/riscv/riscv_zfh_instructions.cc
index 484173e..8432321 100644
--- a/riscv/riscv_zfh_instructions.cc
+++ b/riscv/riscv_zfh_instructions.cc
@@ -514,6 +514,27 @@
   });
 }
 
+// Move a half precision value from an integer register to a float register and
+// NaN box the value.
+void RiscVZfhFMvhx(const Instruction *instruction) {
+  using DstRegValue = RVFpRegister::ValueType;
+  using SrcRegValue = RV32Register::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();
+
+  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);
+}
+
 // Convert from half precision to integer.
 void RiscVZfhCvtWh(const Instruction *instruction) {
   RiscVConvertFloatWithFflagsOp<typename RV32Register::ValueType, HalfFP,
@@ -632,6 +653,27 @@
   });
 }
 
+// Move a half precision value from an integer register to a float register and
+// NaN box the value.
+void RiscVZfhFMvhx(const Instruction *instruction) {
+  using DstRegValue = RVFpRegister::ValueType;
+  using SrcRegValue = RV64Register::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();
+
+  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);
+}
+
 }  // namespace RV64
 
 void RiscVZfhFlhChild(const Instruction *instruction) {
@@ -654,13 +696,6 @@
   reg->data_buffer()->Set<RVFpRegister::ValueType>(0, value);
 }
 
-// Move a half precision value from an integer register to a float register.
-void RiscVZfhFMvhx(const Instruction *instruction) {
-  RiscVUnaryFloatOp<HalfFP, uint64_t>(instruction, [](uint64_t a) -> HalfFP {
-    return HalfFP{.value = static_cast<uint16_t>(a)};
-  });
-}
-
 // Convert from half precision to single precision.
 void RiscVZfhCvtSh(const Instruction *instruction) {
   RiscVZfhCvtHelper<float, HalfFP>(
diff --git a/riscv/riscv_zfh_instructions.h b/riscv/riscv_zfh_instructions.h
index 20d5261..c4e3975 100644
--- a/riscv/riscv_zfh_instructions.h
+++ b/riscv/riscv_zfh_instructions.h
@@ -33,6 +33,12 @@
 void RiscVZfhFMvxh(const Instruction *instruction);
 
 // Source Operands:
+//   rs1: Integer Register
+// Destination Operands:
+//   frd: Float Register
+void RiscVZfhFMvhx(const Instruction *instruction);
+
+// Source Operands:
 //   frs1: Float Register
 //   rm: Literal Operand (rounding mode)
 // Destination Operands:
@@ -102,18 +108,18 @@
 // Destination Operands:
 //   rd: Integer Register
 void RiscVZfhFMvxh(const Instruction *instruction);
-}  // namespace RV64
-
-// Source Operands: *none*
-// Destination Operands:
-//   frd: Float Register
-void RiscVZfhFlhChild(const Instruction *instruction);
 
 // Source Operands:
 //   rs1: Integer Register
 // Destination Operands:
 //   frd: Float Register
 void RiscVZfhFMvhx(const Instruction *instruction);
+}  // namespace RV64
+
+// Source Operands: *none*
+// Destination Operands:
+//   frd: Float Register
+void RiscVZfhFlhChild(const Instruction *instruction);
 
 // Source Operands:
 //   frs1: Float Register
diff --git a/riscv/test/riscv_zfh_instructions_test.cc b/riscv/test/riscv_zfh_instructions_test.cc
index dee6408..4c01542 100644
--- a/riscv/test/riscv_zfh_instructions_test.cc
+++ b/riscv/test/riscv_zfh_instructions_test.cc
@@ -70,7 +70,6 @@
 using ::mpact::sim::riscv::RiscVZfhFmin;
 using ::mpact::sim::riscv::RiscVZfhFmsub;
 using ::mpact::sim::riscv::RiscVZfhFmul;
-using ::mpact::sim::riscv::RiscVZfhFMvhx;
 using ::mpact::sim::riscv::RiscVZfhFnmadd;
 using ::mpact::sim::riscv::RiscVZfhFnmsub;
 using ::mpact::sim::riscv::RiscVZfhFsgnj;
@@ -92,7 +91,6 @@
 using ::mpact::sim::riscv::RV32::RiscVZfhFcmpeq;
 using ::mpact::sim::riscv::RV32::RiscVZfhFcmple;
 using ::mpact::sim::riscv::RV32::RiscVZfhFcmplt;
-using ::mpact::sim::riscv::RV32::RiscVZfhFMvxh;
 
 using ::mpact::sim::riscv::test::FloatingPointToString;
 using ::mpact::sim::riscv::test::FPCompare;
@@ -167,6 +165,9 @@
       std::function<std::tuple<R, uint32_t>(LHS, MHS, RHS)> operation);
 
   uint32_t GetOperationFlags(std::function<void(void)> operation);
+
+  template <typename ScalarRegister, typename FPType>
+  void FmvHxHelper();
 };
 
 template <typename AddressType, typename ValueType>
@@ -440,6 +441,24 @@
   }
 }
 
+template <typename ScalarRegister, typename FPType>
+void RVZfhInstructionTestBase::FmvHxHelper() {
+  using ScalarRegisterType = ScalarRegister::ValueType;
+  AppendRegisterOperands<RVFpRegister>({}, {"f5"});
+  AppendRegisterOperands<ScalarRegister>({"x5"}, {});
+  instruction_->AppendSource(new TestRoundingModeSourceOperand());
+  for (int i = 0; i < 128; i++) {
+    ScalarRegisterType scalar = absl::Uniform<ScalarRegisterType>(bitgen_);
+    SetRegisterValues<ScalarRegisterType, ScalarRegister>({{"x5", scalar}});
+    SetRegisterValues<uint64_t, RVFpRegister>({{"f5", 0}});
+    instruction_->Execute(nullptr);
+    FPType observed_fp = state_->GetRegister<RVFpRegister>("f5")
+                             .first->data_buffer()
+                             ->template Get<FPType>(0);
+    EXPECT_TRUE(std::isnan(observed_fp));
+  }
+}
+
 class RV32ZfhInstructionTest : public RVZfhInstructionTestBase {
  protected:
   // Test conversion instructions. The instance variable semantic_function_ is
@@ -566,7 +585,7 @@
 // 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);
+  SetSemanticFunction(&::mpact::sim::riscv::RV32::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));
@@ -580,13 +599,27 @@
 // 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 {
+  SetSemanticFunction(&::mpact::sim::riscv::RV32::RiscVZfhFMvhx);
+  UnaryOpFPTestHelper<HalfFP, uint32_t>(
+      "fmv.h.x", instruction_, {"x", "f"}, 32, [](uint32_t scalar) -> HalfFP {
         return HalfFP{.value = static_cast<uint16_t>(scalar)};
       });
 }
 
+// Move half precision from an integer register to a float register. Confirm
+// that the destination value is NaN boxed.
+TEST_F(RV32ZfhInstructionTest, RiscVZfhFMvhx_float_nanbox) {
+  SetSemanticFunction(&::mpact::sim::riscv::RV32::RiscVZfhFMvhx);
+  FmvHxHelper<RV32Register, float>();
+}
+
+// Move half precision from an integer register to a float register. Confirm
+// that the destination value is NaN boxed.
+TEST_F(RV32ZfhInstructionTest, RiscVZfhFMvhx_double_nanbox) {
+  SetSemanticFunction(&::mpact::sim::riscv::RV32::RiscVZfhFMvhx);
+  FmvHxHelper<RV32Register, double>();
+}
+
 // Half precision to single precision conversion.
 TEST_F(RV32ZfhInstructionTest, RiscVZfhCvtSh) {
   SetSemanticFunction(&RiscVZfhCvtSh);
@@ -1458,6 +1491,30 @@
       });
 }
 
+// Move half precision from an integer register (lower 16 bits) to a float
+// register.
+TEST_F(RV64ZfhInstructionTest, RiscVZfhFMvhx) {
+  SetSemanticFunction(&::mpact::sim::riscv::RV64::RiscVZfhFMvhx);
+  UnaryOpFPTestHelper<HalfFP, uint64_t>(
+      "fmv.h.x", instruction_, {"x", "f"}, 32, [](uint64_t scalar) -> HalfFP {
+        return HalfFP{.value = static_cast<uint16_t>(scalar)};
+      });
+}
+
+// Move half precision from an integer register to a float register. Confirm
+// that the destination value is NaN boxed.
+TEST_F(RV64ZfhInstructionTest, RiscVZfhFMvhx_float_nanbox) {
+  SetSemanticFunction(&::mpact::sim::riscv::RV64::RiscVZfhFMvhx);
+  FmvHxHelper<RV64Register, float>();
+}
+
+// Move half precision from an integer register to a float register. Confirm
+// that the destination value is NaN boxed.
+TEST_F(RV64ZfhInstructionTest, RiscVZfhFMvhx_double_nanbox) {
+  SetSemanticFunction(&::mpact::sim::riscv::RV64::RiscVZfhFMvhx);
+  FmvHxHelper<RV64Register, double>();
+}
+
 // Test the FP16 load instruction. The semantic functions should match the isa
 // file.
 TEST_F(RV64ZfhInstructionTest, RiscVFlh) {