Add 64bit zfhmin semantic functions

PiperOrigin-RevId: 752452472
Change-Id: I7e6222c30d3431259ac1c98f2b1df8659bf0268c
diff --git a/riscv/riscv_zfh.isa b/riscv/riscv_zfh.isa
index 6ba21c6..9afe769 100644
--- a/riscv/riscv_zfh.isa
+++ b/riscv/riscv_zfh.isa
@@ -84,3 +84,28 @@
   }
 }
 
+slot riscv64_zfh_min: riscv_zfh_min {
+  includes {
+    #include "riscv/riscv_i_instructions.h"
+    #include "riscv/riscv_zfh_instructions.h"
+  }
+  default size = 4;
+  default latency = 0;
+  default opcode =
+    disasm: "Unimplemented instruction at 0x%(@:08x)",
+    semfunc: "&RV32VUnimplementedInstruction";
+  opcodes {
+    flh{(: rs1, I_imm12 : ), (: : frd)},
+      resources: {next_pc, rs1 : frd[0..]},
+      semfunc: "&RV64::RiscVILhu", "&RV64::RiscVZfhFlhChild",  // new
+      disasm: "flh", "%frd, %I_imm12(%rs1)";
+    fsh{: rs1, S_imm12, frs2},
+      resources: {next_pc, rs1, frs2},
+      semfunc: "&RV64::RiscVISh",  // new
+      disasm: "fsh", "%frs2, %S_imm12(%rs1)";
+    fmv_xh{: frs1 : rd},
+      resources: {next_pc, frs1 : rd[0..]},
+      semfunc: "&RV64::RiscVZfhFMvxh",  // new
+      disasm: "fmv.x.h", "%rd, %frs1";
+  }
+}
diff --git a/riscv/riscv_zfh_instructions.cc b/riscv/riscv_zfh_instructions.cc
index 34ce19d..8f20a82 100644
--- a/riscv/riscv_zfh_instructions.cc
+++ b/riscv/riscv_zfh_instructions.cc
@@ -147,6 +147,21 @@
 
 }  // namespace RV32
 
+namespace RV64 {
+// Move a half precision value from a float register to a 32 bit integer
+// register.
+void RiscVZfhFMvxh(const Instruction *instruction) {
+  RiscVUnaryFloatOp<uint64_t, HalfFP>(instruction, [](HalfFP a) -> uint64_t {
+    if (FPTypeInfo<HalfFP>::SignBit(a)) {
+      // Repeat the sign bit for negative values.
+      return 0xFFFF'FFFF'FFFF'0000 | a.value;
+    }
+    return static_cast<uint64_t>(a.value);
+  });
+}
+
+}  // namespace RV64
+
 void RiscVZfhFlhChild(const Instruction *instruction) {
   using FPUInt = typename FPTypeInfo<HalfFP>::UIntType;
   LoadContext *context = static_cast<LoadContext *>(instruction->context());
diff --git a/riscv/riscv_zfh_instructions.h b/riscv/riscv_zfh_instructions.h
index 536dbc3..9181490 100644
--- a/riscv/riscv_zfh_instructions.h
+++ b/riscv/riscv_zfh_instructions.h
@@ -29,17 +29,64 @@
 using HalfFP = ::mpact::sim::generic::HalfFP;
 
 namespace RV32 {
+// Source Operands:
+//   frs1: Float Register
+// Destination Operands:
+//   rd: Integer Register
 void RiscVZfhFMvxh(const Instruction *instruction);
 }  // namespace RV32
 
-namespace RV64 {}  // namespace RV64
+namespace RV64 {
+// Source Operands:
+//   frs1: Float Register
+// 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);
+
+// Source Operands:
+//   frs1: Float Register
+//   rm: Literal Operand (rounding mode)
+// Destination Operands:
+//   frd: Float Register
+//   fflags: Accrued Exception Flags field in FCSR
 void RiscVZfhCvtSh(const Instruction *instruction);
+
+// Source Operands:
+//   frs1: Float Register
+//   rm: Literal Operand (rounding mode)
+// Destination Operands:
+//   frd: Float Register
+//   fflags: Accrued Exception Flags field in FCSR
 void RiscVZfhCvtHs(const Instruction *instruction);
+
+// Source Operands:
+//   frs1: Float Register
+//   rm: Literal Operand (rounding mode)
+// Destination Operands:
+//   frd: Float Register
+//   fflags: Accrued Exception Flags field in FCSR
 void RiscVZfhCvtDh(const Instruction *instruction);
+
+// Source Operands:
+//   frs1: Float Register
+//   rm: Literal Operand (rounding mode)
+// Destination Operands:
+//   frd: Float Register
+//   fflags: Accrued Exception Flags field in FCSR
 void RiscVZfhCvtHd(const Instruction *instruction);
+
 // TODO(b/409778536): Factor out generic unimplemented instruction semantic
 //                    function.
 void RV32VUnimplementedInstruction(const Instruction *instruction);
diff --git a/riscv/test/riscv_zfh_instructions_test.cc b/riscv/test/riscv_zfh_instructions_test.cc
index 1aaa685..8b02448 100644
--- a/riscv/test/riscv_zfh_instructions_test.cc
+++ b/riscv/test/riscv_zfh_instructions_test.cc
@@ -38,6 +38,9 @@
 #include "riscv/riscv_register.h"
 #include "riscv/test/riscv_fp_test_base.h"
 
+// This file contains the unit tests for the RiscV ZFH extension instructions.
+// Testing is focused on the instruction semantic functions.
+
 namespace {
 
 using ::mpact::sim::generic::operator*;  // NOLINT: is used below.
@@ -53,6 +56,7 @@
 using ::mpact::sim::riscv::RiscVZfhFlhChild;
 using ::mpact::sim::riscv::RiscVZfhFMvhx;
 using ::mpact::sim::riscv::RV32Register;
+using ::mpact::sim::riscv::RV64Register;
 using ::mpact::sim::riscv::RVFpRegister;
 using ::mpact::sim::riscv::RV32::RiscVILhu;
 using ::mpact::sim::riscv::RV32::RiscVZfhFMvxh;
@@ -67,6 +71,50 @@
 const int kRoundingModeRoundDown = static_cast<int>(FPRoundingMode::kRoundDown);
 const int kRoundingModeRoundUp = static_cast<int>(FPRoundingMode::kRoundUp);
 
+class RVZfhInstructionTestBase : public RiscVFPInstructionTestBase {
+ protected:
+  template <typename AddressType, typename ValueType>
+  void SetupMemory(AddressType, ValueType);
+
+  template <typename ReturnType, typename IntegerRegister>
+  ReturnType LoadHalfHelper(typename IntegerRegister::ValueType, int16_t);
+};
+
+template <typename AddressType, typename ValueType>
+void RVZfhInstructionTestBase::SetupMemory(AddressType address,
+                                           ValueType value) {
+  DataBuffer *mem_db = state_->db_factory()->Allocate<ValueType>(1);
+  mem_db->Set<ValueType>(0, value);
+  state_->StoreMemory(instruction_, address, mem_db);
+  mem_db->DecRef();
+}
+
+template <typename ReturnType, typename IntegerRegister>
+ReturnType RVZfhInstructionTestBase::LoadHalfHelper(
+    typename IntegerRegister::ValueType base, int16_t offset) {
+  // Technically the offset for FL* is a 12 bit signed integer but we'll use 16
+  // bits for testing.
+  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<uint64_t, RVFpRegister>({{kFrdName, 0}});
+
+  instruction_->Execute(nullptr);
+
+  ReturnType observed_val = state_->GetRegister<RVFpRegister>(kFrdName)
+                                .first->data_buffer()
+                                ->template Get<ReturnType>(0);
+  return observed_val;
+}
+
 // 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.
@@ -104,7 +152,7 @@
   FPRoundingMode rounding_mode_;
 };
 
-class RV32ZfhInstructionTest : public RiscVFPInstructionTestBase {
+class RV32ZfhInstructionTest : public RVZfhInstructionTestBase {
  protected:
   // Test conversion instructions. The instance variable semantic_function_ is
   // used to set the semantic function for the instruction and should be set
@@ -146,12 +194,6 @@
   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;
 };
 
@@ -191,37 +233,6 @@
       << " 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) {
@@ -229,7 +240,7 @@
   SetChildInstruction();
   SetChildSemanticFunction(&RiscVZfhFlhChild);
 
-  SetupMemory<uint16_t>(0xFF, 0xBEEF);
+  SetupMemory<uint32_t, uint16_t>(0xFF, 0xBEEF);
 
   HalfFP observed_val =
       LoadHalfHelper<HalfFP, RV32Register>(/* base */ 0x0, /* offset */ 0xFF);
@@ -243,7 +254,7 @@
   SetChildInstruction();
   SetChildSemanticFunction(&RiscVZfhFlhChild);
 
-  SetupMemory<uint16_t>(0xFF, 0xBEEF);
+  SetupMemory<uint32_t, uint16_t>(0xFF, 0xBEEF);
 
   float observed_val =
       LoadHalfHelper<float, RV32Register>(/* base */ 0xFF, /* offset */ 0);
@@ -257,7 +268,7 @@
   SetChildInstruction();
   SetChildSemanticFunction(&RiscVZfhFlhChild);
 
-  SetupMemory<uint16_t>(0xFF, 0xBEEF);
+  SetupMemory<uint32_t, uint16_t>(0xFF, 0xBEEF);
 
   double observed_val = LoadHalfHelper<double, RV32Register>(
       /* base */ 0x0100, /* offset */ -1);
@@ -553,4 +564,62 @@
   EXPECT_EQ(absl::bit_cast<uint16_t>(actual_n0), expected_n0);
 }
 
+class RV64ZfhInstructionTest : public RVZfhInstructionTestBase {};
+
+// Move half precision from a float register to an integer register. The IEEE754
+// encoding is preserved in the integer register.
+TEST_F(RV64ZfhInstructionTest, RiscVZfhFMvxh) {
+  SetSemanticFunction(&mpact::sim::riscv::RV64::RiscVZfhFMvxh);
+  UnaryOpFPTestHelper<uint64_t, HalfFP>(
+      "fmv.x.h", instruction_, {"f", "x"}, 32, [](HalfFP half_fp) -> uint64_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.
+        uint64_t result = sign ? 0xFFFF'FFFF'FFFF'0000 : 0;
+        result |= static_cast<uint64_t>(half_fp.value);
+        return result;
+      });
+}
+
+// Test the FP16 load instruction. The semantic functions should match the isa
+// file.
+TEST_F(RV64ZfhInstructionTest, RiscVFlh) {
+  SetSemanticFunction(&RiscVILhu);
+  SetChildInstruction();
+  SetChildSemanticFunction(&RiscVZfhFlhChild);
+
+  SetupMemory<uint64_t, uint16_t>(0xFF, 0xBEEF);
+
+  HalfFP observed_val =
+      LoadHalfHelper<HalfFP, RV64Register>(/* 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(RV64ZfhInstructionTest, RiscVFlh_float_nanbox) {
+  SetSemanticFunction(&RiscVILhu);
+  SetChildInstruction();
+  SetChildSemanticFunction(&RiscVZfhFlhChild);
+
+  SetupMemory<uint64_t, uint16_t>(0xFF, 0xBEEF);
+
+  float observed_val =
+      LoadHalfHelper<float, RV64Register>(/* 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(RV64ZfhInstructionTest, RiscVFlh_double_nanbox) {
+  SetSemanticFunction(&RiscVILhu);
+  SetChildInstruction();
+  SetChildSemanticFunction(&RiscVZfhFlhChild);
+
+  SetupMemory<uint64_t, uint16_t>(0xFF, 0xBEEF);
+
+  double observed_val = LoadHalfHelper<double, RV64Register>(
+      /* base */ 0x0100, /* offset */ -1);
+  EXPECT_TRUE(std::isnan(observed_val));
+}
+
 }  // namespace