zfh: Support both XLENs for all semantic functions that touch a scalar regsiter

PiperOrigin-RevId: 760766479
Change-Id: I9e399582977f433835c342618e8b95fe39033096
diff --git a/riscv/riscv_zfh.isa b/riscv/riscv_zfh.isa
index a6ecf23..76265d5 100644
--- a/riscv/riscv_zfh.isa
+++ b/riscv/riscv_zfh.isa
@@ -25,7 +25,7 @@
 // First disasm field is 18 char wide and left justified.
 disasm widths = {-18};
 
-slot riscv_zfh_min {
+slot riscv_zfh_min_common {
   includes {
     #include "riscv/riscv_zfh_instructions.h"
   }
@@ -54,7 +54,7 @@
   }
 }
 
-slot riscv32_zfh_min: riscv_zfh_min {
+slot riscv32_zfh_min: riscv_zfh_min_common {
   includes {
     #include "riscv/riscv_i_instructions.h"
     #include "riscv/riscv_zfh_instructions.h"
@@ -84,7 +84,7 @@
   }
 }
 
-slot riscv64_zfh_min: riscv_zfh_min {
+slot riscv64_zfh_min: riscv_zfh_min_common {
   includes {
     #include "riscv/riscv_i_instructions.h"
     #include "riscv/riscv_zfh_instructions.h"
@@ -114,7 +114,7 @@
   }
 }
 
-slot riscv32_zfh : riscv32_zfh_min {
+slot riscv_zfh_common {
   includes {
     #include "riscv/riscv_zfh_instructions.h"
   }
@@ -124,6 +124,14 @@
     disasm: "Unimplemented instruction at 0x%(@:08x)",
     semfunc: "&RV32VUnimplementedInstruction";
   opcodes {
+    fcvt_hw{: rs1, rm : frd, fflags},
+      resources: {next_pc, frs1 : frd[0..]},
+      semfunc: "&RiscVZfhCvtHw",
+      disasm: "fcvt.h.w", "%frd, %rs1";
+    fcvt_hwu{: rs1, rm : frd, fflags},
+      resources: {next_pc, frs1 : frd[0..]},
+      semfunc: "&RiscVZfhCvtHwu",
+      disasm: "fcvt.h.wu", "%frd, %rs1";
     fadd_h{: frs1, frs2, rm : frd, fflags},
       resources: {next_pc, frs1, frs2 : frd[0..]},
       semfunc: "&RiscVZfhFadd",
@@ -152,22 +160,6 @@
       resources: {next_pc, frs1 : frd[0..]},
       semfunc: "&RiscVZfhFsqrt",
       disasm: "fsqrt.h", "%frd, %frs1";
-    fcvt_hw{: rs1, rm : frd, fflags},
-      resources: {next_pc, frs1 : frd[0..]},
-      semfunc: "&RV32::RiscVZfhCvtHw",
-      disasm: "fcvt.h.w", "%frd, %rs1";
-    fcvt_wh{: frs1, rm : rd, fflags},
-      resources: {next_pc, frs1 : rd[0..]},
-      semfunc: "&RV32::RiscVZfhCvtWh",
-      disasm: "fcvt.w.h", "%rd, %frs1";
-    fcvt_hwu{: rs1, rm : frd, fflags},
-      resources: {next_pc, frs1 : frd[0..]},
-      semfunc: "&RV32::RiscVZfhCvtHwu",
-      disasm: "fcvt.h.wu", "%frd, %rs1";
-    fcvt_wuh{: frs1, rm : rd, fflags},
-      resources: {next_pc, frs1 : rd[0..]},
-      semfunc: "&RV32::RiscVZfhCvtWuh",
-      disasm: "fcvt.wu.h", "%rd, %frs1";
     fsgnj_h{: frs1, frs2 : frd },
       resources: {next_pc, frs1, frs2 : frd[0..]},
       semfunc: "&RiscVZfhFsgnj",
@@ -180,22 +172,6 @@
       resources: {next_pc, frs1, frs2 : frd[0..]},
       semfunc: "&RiscVZfhFsgnjx",
       disasm: "fsgnjnx.h", "%frd, %frs1, %frs2";
-    fcmpeq_h{: frs1, frs2 : rd, fflags},
-      resources: { next_pc, frs1, frs2 : rd[0..]},
-      semfunc: "&RV32::RiscVZfhFcmpeq",
-      disasm: "feq.h", "%rd, %frs1, %frs2";
-    fcmplt_h{: frs1, frs2 : rd, fflags},
-      resources: { next_pc, frs1, frs2 : rd[0..]},
-      semfunc: "&RV32::RiscVZfhFcmplt",
-      disasm: "flt.h", "%rd, %frs1, %frs2";
-    fcmple_h{: frs1, frs2 : rd, fflags},
-      resources: { next_pc, frs1, frs2 : rd[0..]},
-      semfunc: "&RV32::RiscVZfhFcmple",
-      disasm: "fle.h", "%rd, %frs1, %frs2";
-    fclass_h{: frs1 : rd},
-      resources: { next_pc, frs1 : rd[0..]},
-      semfunc: "&RV32::RiscVZfhFclass",
-      disasm: "fclass.h", "%rd, %frs1";
     fmadd_h{: frs1, frs2, frs3, rm : frd, fflags},
       resources: {next_pc, frs1, frs2, frs3 : frd[0..]},
       semfunc: "&RiscVZfhFmadd",
@@ -214,3 +190,77 @@
       disasm: "fnmsub.h", "%frd, %frs1, %frs2, %frs3";
   }
 }
+
+slot riscv32_zfh : riscv32_zfh_min, riscv_zfh_common {
+  includes {
+    #include "riscv/riscv_zfh_instructions.h"
+  }
+  default size = 4;
+  default latency = 0;
+  default opcode =
+    disasm: "Unimplemented instruction at 0x%(@:08x)",
+    semfunc: "&RV32VUnimplementedInstruction";
+  opcodes {
+    fcvt_wh{: frs1, rm : rd, fflags},
+      resources: {next_pc, frs1 : rd[0..]},
+      semfunc: "&RV32::RiscVZfhCvtWh",
+      disasm: "fcvt.w.h", "%rd, %frs1";
+    fcvt_wuh{: frs1, rm : rd, fflags},
+      resources: {next_pc, frs1 : rd[0..]},
+      semfunc: "&RV32::RiscVZfhCvtWuh",
+      disasm: "fcvt.wu.h", "%rd, %frs1";
+    fcmpeq_h{: frs1, frs2 : rd, fflags},
+      resources: { next_pc, frs1, frs2 : rd[0..]},
+      semfunc: "&RV32::RiscVZfhFcmpeq",
+      disasm: "feq.h", "%rd, %frs1, %frs2";
+    fcmplt_h{: frs1, frs2 : rd, fflags},
+      resources: { next_pc, frs1, frs2 : rd[0..]},
+      semfunc: "&RV32::RiscVZfhFcmplt",
+      disasm: "flt.h", "%rd, %frs1, %frs2";
+    fcmple_h{: frs1, frs2 : rd, fflags},
+      resources: { next_pc, frs1, frs2 : rd[0..]},
+      semfunc: "&RV32::RiscVZfhFcmple",
+      disasm: "fle.h", "%rd, %frs1, %frs2";
+    fclass_h{: frs1 : rd},
+      resources: { next_pc, frs1 : rd[0..]},
+      semfunc: "&RV32::RiscVZfhFclass",
+      disasm: "fclass.h", "%rd, %frs1";
+  }
+}
+
+slot riscv64_zfh : riscv64_zfh_min, riscv_zfh_common {
+  includes {
+    #include "riscv/riscv_zfh_instructions.h"
+  }
+  default size = 4;
+  default latency = 0;
+  default opcode =
+    disasm: "Unimplemented instruction at 0x%(@:08x)",
+    semfunc: "&RV32VUnimplementedInstruction";
+  opcodes {
+    fcvt_wh{: frs1, rm : rd, fflags},
+      resources: {next_pc, frs1 : rd[0..]},
+      semfunc: "&RV64::RiscVZfhCvtWh",
+      disasm: "fcvt.w.h", "%rd, %frs1";
+    fcvt_wuh{: frs1, rm : rd, fflags},
+      resources: {next_pc, frs1 : rd[0..]},
+      semfunc: "&RV64::RiscVZfhCvtWuh",
+      disasm: "fcvt.wu.h", "%rd, %frs1";
+    fcmpeq_h{: frs1, frs2 : rd, fflags},
+      resources: { next_pc, frs1, frs2 : rd[0..]},
+      semfunc: "&RV64::RiscVZfhFcmpeq",
+      disasm: "feq.h", "%rd, %frs1, %frs2";
+    fcmplt_h{: frs1, frs2 : rd, fflags},
+      resources: { next_pc, frs1, frs2 : rd[0..]},
+      semfunc: "&RV64::RiscVZfhFcmplt",
+      disasm: "flt.h", "%rd, %frs1, %frs2";
+    fcmple_h{: frs1, frs2 : rd, fflags},
+      resources: { next_pc, frs1, frs2 : rd[0..]},
+      semfunc: "&RV64::RiscVZfhFcmple",
+      disasm: "fle.h", "%rd, %frs1, %frs2";
+    fclass_h{: frs1 : rd},
+      resources: { next_pc, frs1 : rd[0..]},
+      semfunc: "&RV64::RiscVZfhFclass",
+      disasm: "fclass.h", "%rd, %frs1";
+  }
+}
diff --git a/riscv/riscv_zfh_instructions.cc b/riscv/riscv_zfh_instructions.cc
index 8432321..23af707 100644
--- a/riscv/riscv_zfh_instructions.cc
+++ b/riscv/riscv_zfh_instructions.cc
@@ -43,7 +43,6 @@
 using HalfFP = ::mpact::sim::generic::HalfFP;
 using ::mpact::sim::generic::IsMpactFp;
 using ::mpact::sim::generic::operator*;  // NOLINT: is used below (clang error).
-using ::mpact::sim::generic::IsMpactFp;
 
 namespace {
 
@@ -498,27 +497,24 @@
   fflags_dest->GetRiscVCsr()->SetBits(fflags);
 }
 
-}  // namespace
-
-namespace RV32 {
-
-// Move a half precision value from a float register to a 32 bit integer
-// register.
-void RiscVZfhFMvxh(const Instruction *instruction) {
-  RiscVUnaryFloatOp<uint32_t, HalfFP>(instruction, [](HalfFP a) -> uint32_t {
+// 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 0xFFFF'0000 | a.value;
+      return (std::numeric_limits<XRegValue>::max() << 16) | a.value;
     }
-    return static_cast<uint32_t>(a.value);
+    return static_cast<XRegValue>(a.value);
   });
 }
 
-// Move a half precision value from an integer register to a float register and
-// NaN box the value.
-void RiscVZfhFMvhx(const Instruction *instruction) {
+// Move a half precision value from an integer register to a float register
+template <typename XRegister>
+inline void RiscVZfhFMvhxHelper(const Instruction *instruction) {
   using DstRegValue = RVFpRegister::ValueType;
-  using SrcRegValue = RV32Register::ValueType;
+  using SrcRegValue = XRegister::ValueType;
   SrcRegValue lhs = generic::GetInstructionSource<SrcRegValue>(instruction, 0);
   HalfFP dest_value = {.value = static_cast<uint16_t>(lhs)};
 
@@ -526,6 +522,7 @@
                   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);
@@ -535,33 +532,101 @@
   reg->data_buffer()->template Set<DstRegValue>(0, reg_value);
 }
 
-// Convert from half precision to integer.
+// Compare two half precision values for equality.
+template <typename XRegister>
+inline void RiscVZfhFcmpeqHelper(const Instruction *instruction) {
+  using DstRegValue = 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 = 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 = 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 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 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);
-      });
-}
-
-// Convert from half precision to unsigned integer.
+// Convert from half precision to unsigned 32 bit integer.
 void RiscVZfhCvtWuh(const Instruction *instruction) {
   RiscVConvertFloatWithFflagsOp<typename RV32Register::ValueType, HalfFP,
                                 uint32_t>(instruction);
@@ -569,65 +634,17 @@
 
 // Compare two half precision values for equality.
 void RiscVZfhFcmpeq(const Instruction *instruction) {
-  RiscVBinaryFloatNaNBoxOp<RVFpRegister::ValueType, uint64_t, HalfFP>(
-      instruction, [](HalfFP a, HalfFP b) -> uint64_t {
-        float a_f;
-        float b_f;
-        uint32_t unused_fflags = 0;
-        if (FPTypeInfo<HalfFP>::IsSNaN(a)) {
-          a_f = absl::bit_cast<float>(FPTypeInfo<float>::kPosInf | 1);
-        } else {
-          a_f = ConvertFromHalfFP<float>(a, unused_fflags);
-        }
-        if (FPTypeInfo<HalfFP>::IsSNaN(b)) {
-          b_f = absl::bit_cast<float>(FPTypeInfo<float>::kPosInf | 1);
-        } else {
-          b_f = ConvertFromHalfFP<float>(b, unused_fflags);
-        }
-        return a_f == b_f ? 1 : 0;
-      });
+  RiscVZfhFcmpeqHelper<RV32Register>(instruction);
 }
 
 // Compare two half precision values for less than.
 void RiscVZfhFcmplt(const Instruction *instruction) {
-  RiscVBinaryFloatNaNBoxOp<RVFpRegister::ValueType, uint64_t, HalfFP>(
-      instruction, [](HalfFP a, HalfFP b) -> uint64_t {
-        float a_f;
-        float b_f;
-        uint32_t unused_fflags = 0;
-        if (FPTypeInfo<HalfFP>::IsNaN(a)) {
-          a_f = absl::bit_cast<float>(FPTypeInfo<float>::kPosInf | 1);
-        } else {
-          a_f = ConvertFromHalfFP<float>(a, unused_fflags);
-        }
-        if (FPTypeInfo<HalfFP>::IsNaN(b)) {
-          b_f = absl::bit_cast<float>(FPTypeInfo<float>::kPosInf | 1);
-        } else {
-          b_f = ConvertFromHalfFP<float>(b, unused_fflags);
-        }
-        return a_f < b_f ? 1 : 0;
-      });
+  RiscVZfhFcmpltHelper<RV32Register>(instruction);
 }
 
 // Compare two half precision values for less than or equal to.
 void RiscVZfhFcmple(const Instruction *instruction) {
-  RiscVBinaryFloatNaNBoxOp<RVFpRegister::ValueType, uint64_t, HalfFP>(
-      instruction, [](HalfFP a, HalfFP b) -> uint64_t {
-        float a_f;
-        float b_f;
-        uint32_t unused_fflags = 0;
-        if (FPTypeInfo<HalfFP>::IsNaN(a)) {
-          a_f = absl::bit_cast<float>(FPTypeInfo<float>::kPosInf | 1);
-        } else {
-          a_f = ConvertFromHalfFP<float>(a, unused_fflags);
-        }
-        if (FPTypeInfo<HalfFP>::IsNaN(b)) {
-          b_f = absl::bit_cast<float>(FPTypeInfo<float>::kPosInf | 1);
-        } else {
-          b_f = ConvertFromHalfFP<float>(b, unused_fflags);
-        }
-        return a_f <= b_f ? 1 : 0;
-      });
+  RiscVZfhFcmpleHelper<RV32Register>(instruction);
 }
 
 // Classify a half precision value.
@@ -644,34 +661,48 @@
 // 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);
-  });
+  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) {
-  using DstRegValue = RVFpRegister::ValueType;
-  using SrcRegValue = RV64Register::ValueType;
-  SrcRegValue lhs = generic::GetInstructionSource<SrcRegValue>(instruction, 0);
-  HalfFP dest_value = {.value = static_cast<uint16_t>(lhs)};
+  RiscVZfhFMvhxHelper<RV64Register>(instruction);
+}
 
-  auto *reg = static_cast<generic::RegisterDestinationOperand<DstRegValue> *>(
-                  instruction->Destination(0))
-                  ->GetRegister();
+// Convert from half precision to signed 32 bit integer.
+void RiscVZfhCvtWh(const Instruction *instruction) {
+  RiscVConvertFloatWithFflagsOp<typename RV64Register::ValueType, HalfFP,
+                                int32_t>(instruction);
+}
 
-  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 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));
+      });
 }
 
 }  // namespace RV64
@@ -863,6 +894,26 @@
       [](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) {
diff --git a/riscv/riscv_zfh_instructions.h b/riscv/riscv_zfh_instructions.h
index c4e3975..f4e67c8 100644
--- a/riscv/riscv_zfh_instructions.h
+++ b/riscv/riscv_zfh_instructions.h
@@ -47,22 +47,6 @@
 void RiscVZfhCvtWh(const Instruction *instruction);
 
 // Source Operands:
-//   rs1: Integer Register
-//   rm: Literal Operand (rounding mode)
-// Destination Operands:
-//   frd: Float Register
-//   fflags: Accrued Exception Flags field in FCSR
-void RiscVZfhCvtHw(const Instruction *instruction);
-
-// Source Operands:
-//   rs1: Integer Register
-//   rm: Literal Operand (rounding mode)
-// Destination Operands:
-//   frd: Float Register
-//   fflags: Accrued Exception Flags field in FCSR
-void RiscVZfhCvtHwu(const Instruction *instruction);
-
-// Source Operands:
 //   frs1: Float Register
 //   rm: Literal Operand (rounding mode)
 // Destination Operands:
@@ -114,6 +98,53 @@
 // Destination Operands:
 //   frd: Float Register
 void RiscVZfhFMvhx(const Instruction *instruction);
+
+// Source Operands:
+//   frs1: Float Register
+//   rm: Literal Operand (rounding mode)
+// Destination Operands:
+//   rd: Integer Register
+//   fflags: Accrued Exception Flags field in FCSR
+void RiscVZfhCvtWh(const Instruction *instruction);
+
+// Source Operands:
+//   frs1: Float Register
+//   rm: Literal Operand (rounding mode)
+// Destination Operands:
+//   rd: Integer Register
+//   fflags: Accrued Exception Flags field in FCSR
+void RiscVZfhCvtWuh(const Instruction *instruction);
+
+// Source Operands:
+//   frs1: Float Register
+//   frs2: Float Register
+// Destination Operands:
+//   rd: Integer Register
+//   fflags: Accrued Exception Flags field in FCSR
+void RiscVZfhFcmpeq(const Instruction *instruction);
+
+// Source Operands:
+//   frs1: Float Register
+//   frs2: Float Register
+// Destination Operands:
+//   rd: Integer Register
+//   fflags: Accrued Exception Flags field in FCSR
+void RiscVZfhFcmplt(const Instruction *instruction);
+
+// Source Operands:
+//   frs1: Float Register
+//   frs2: Float Register
+// Destination Operands:
+//   rd: Integer Register
+//   fflags: Accrued Exception Flags field in FCSR
+void RiscVZfhFcmple(const Instruction *instruction);
+
+// Source Operands:
+//   frs1: Float Register
+// Destination Operands:
+//   rd: Integer Register
+void RiscVZfhFclass(const Instruction *instruction);
+
 }  // namespace RV64
 
 // Source Operands: *none*
@@ -280,6 +311,22 @@
 //   fflags: Accrued Exception Flags field in FCSR
 void RiscVZfhFnmsub(const Instruction *instruction);
 
+// Source Operands:
+//   rs1: Integer Register
+//   rm: Literal Operand (rounding mode)
+// Destination Operands:
+//   frd: Float Register
+//   fflags: Accrued Exception Flags field in FCSR
+void RiscVZfhCvtHw(const Instruction *instruction);
+
+// Source Operands:
+//   rs1: Integer Register
+//   rm: Literal Operand (rounding mode)
+// Destination Operands:
+//   frd: Float Register
+//   fflags: Accrued Exception Flags field in FCSR
+void RiscVZfhCvtHwu(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 4c01542..1e80aa3 100644
--- a/riscv/test/riscv_zfh_instructions_test.cc
+++ b/riscv/test/riscv_zfh_instructions_test.cc
@@ -61,6 +61,8 @@
 using ::mpact::sim::riscv::RiscVZfhCvtDh;
 using ::mpact::sim::riscv::RiscVZfhCvtHd;
 using ::mpact::sim::riscv::RiscVZfhCvtHs;
+using ::mpact::sim::riscv::RiscVZfhCvtHw;
+using ::mpact::sim::riscv::RiscVZfhCvtHwu;
 using ::mpact::sim::riscv::RiscVZfhCvtSh;
 using ::mpact::sim::riscv::RiscVZfhFadd;
 using ::mpact::sim::riscv::RiscVZfhFdiv;
@@ -82,15 +84,6 @@
 using ::mpact::sim::riscv::RVFpRegister;
 using ::mpact::sim::riscv::ScopedFPRoundingMode;
 using ::mpact::sim::riscv::ScopedFPStatus;
-using ::mpact::sim::riscv::RV32::RiscVILhu;
-using ::mpact::sim::riscv::RV32::RiscVZfhCvtHw;
-using ::mpact::sim::riscv::RV32::RiscVZfhCvtHwu;
-using ::mpact::sim::riscv::RV32::RiscVZfhCvtWh;
-using ::mpact::sim::riscv::RV32::RiscVZfhCvtWuh;
-using ::mpact::sim::riscv::RV32::RiscVZfhFclass;
-using ::mpact::sim::riscv::RV32::RiscVZfhFcmpeq;
-using ::mpact::sim::riscv::RV32::RiscVZfhFcmple;
-using ::mpact::sim::riscv::RV32::RiscVZfhFcmplt;
 
 using ::mpact::sim::riscv::test::FloatingPointToString;
 using ::mpact::sim::riscv::test::FPCompare;
@@ -167,7 +160,28 @@
   uint32_t GetOperationFlags(std::function<void(void)> operation);
 
   template <typename ScalarRegister, typename FPType>
-  void FmvHxHelper();
+  void FmvHxNanBoxHelper();
+
+  template <typename ScalarRegister>
+  void FmvXhHelper();
+
+  template <typename ScalarRegister, typename SourceIntegerType>
+  void CvtHwHelper(absl::string_view);
+
+  template <typename ScalarRegister, typename DestinationIntegerType>
+  void CvtWhHelper(absl::string_view);
+
+  template <typename ScalarRegister>
+  void CmpEqHelper();
+
+  template <typename ScalarRegister>
+  void CmpLtHelper();
+
+  template <typename ScalarRegister>
+  void CmpLeHelper();
+
+  template <typename ScalarRegister>
+  void ClassHelper();
 };
 
 template <typename AddressType, typename ValueType>
@@ -441,8 +455,9 @@
   }
 }
 
+// Verify that the fmv.h.x instruction NaN boxes the converted value.
 template <typename ScalarRegister, typename FPType>
-void RVZfhInstructionTestBase::FmvHxHelper() {
+void RVZfhInstructionTestBase::FmvHxNanBoxHelper() {
   using ScalarRegisterType = ScalarRegister::ValueType;
   AppendRegisterOperands<RVFpRegister>({}, {"f5"});
   AppendRegisterOperands<ScalarRegister>({"x5"}, {});
@@ -459,6 +474,165 @@
   }
 }
 
+// Helper to test the movement of an IEEE encoded half precision value from a
+// float register to an integer register.
+template <typename ScalarRegister>
+void RVZfhInstructionTestBase::FmvXhHelper() {
+  using ScalarRegisterType = ScalarRegister::ValueType;
+  UnaryOpFPTestHelper<ScalarRegisterType, HalfFP>(
+      "fmv.x.h", instruction_, {"f", "x"}, 32,
+      [](HalfFP half_fp) -> ScalarRegisterType {
+        bool sign = 1 & (half_fp.value >> (FPTypeInfo<HalfFP>::kBitSize - 1));
+        // Fill the upper XLEN-16 bits with the sign bit as per the spec.
+        ScalarRegisterType result =
+            sign ? (std::numeric_limits<ScalarRegisterType>::max() << 16) : 0;
+        result |= static_cast<ScalarRegisterType>(half_fp.value);
+        return result;
+      });
+}
+
+// Helper to test conversion of a 32bit integer value to a half precision float
+// value.
+template <typename ScalarRegister, typename SourceIntegerType>
+void RVZfhInstructionTestBase::CvtHwHelper(absl::string_view name) {
+  UnaryOpWithFflagsMixedTestHelper<RVFpRegister, ScalarRegister, HalfFP,
+                                   SourceIntegerType>(
+      name, instruction_, {"x", "f"}, 32,
+      [](SourceIntegerType input_int, int rm) -> std::tuple<HalfFP, uint32_t> {
+        uint32_t fflags = 0;
+        HalfFP result = FpConversionsTestHelper(static_cast<double>(input_int))
+                            .ConvertWithFlags<HalfFP>(
+                                fflags, static_cast<FPRoundingMode>(rm));
+        return std::make_tuple(result, fflags);
+      });
+}
+
+// Helper to test conversion of a half precision float value to a 32bit integer
+// value.
+template <typename ScalarRegister, typename DestinationIntegerType>
+void RVZfhInstructionTestBase::CvtWhHelper(absl::string_view name) {
+  UnaryOpWithFflagsMixedTestHelper<ScalarRegister, RVFpRegister,
+                                   DestinationIntegerType, HalfFP>(
+      name, instruction_, {"f", "x"}, 32,
+      [this](HalfFP input,
+             int rm) -> std::tuple<DestinationIntegerType, uint32_t> {
+        uint32_t fflags = 0;
+        double input_double =
+            FpConversionsTestHelper(input).ConvertWithFlags<double>(fflags);
+        const DestinationIntegerType val =
+            RoundToInteger<double, DestinationIntegerType>(input_double, rm,
+                                                           fflags);
+        return std::make_tuple(val, fflags);
+      });
+}
+
+// Helper to test the comparison of two half precision values for equality.
+template <typename ScalarRegister>
+void RVZfhInstructionTestBase::CmpEqHelper() {
+  using ScalarRegisterType = ScalarRegister::ValueType;
+  BinaryOpWithFflagsFPTestHelper<ScalarRegisterType, HalfFP, HalfFP>(
+      "feq.h", instruction_, {"f", "f", "x"}, 32,
+      [](HalfFP a, HalfFP b) -> std::tuple<ScalarRegisterType, uint32_t> {
+        uint32_t fflags = 0;
+        double a_f =
+            FpConversionsTestHelper(a).ConvertWithFlags<double>(fflags);
+        double b_f =
+            FpConversionsTestHelper(b).ConvertWithFlags<double>(fflags);
+        ScalarRegisterType result = a_f == b_f ? 1 : 0;
+        if (std::isnan(a_f) || std::isnan(b_f)) {
+          result = 0;
+        }
+        return std::make_tuple(result, fflags);
+      });
+}
+
+// Helper to test the comparison of two half precision values for less than.
+template <typename ScalarRegister>
+void RVZfhInstructionTestBase::CmpLtHelper() {
+  using ScalarRegisterType = ScalarRegister::ValueType;
+  BinaryOpWithFflagsFPTestHelper<ScalarRegisterType, HalfFP, HalfFP>(
+      "flt.h", instruction_, {"f", "f", "x"}, 32,
+      [](HalfFP a, HalfFP b) -> std::tuple<ScalarRegisterType, uint32_t> {
+        uint32_t fflags = 0;
+        double a_f =
+            FpConversionsTestHelper(a).ConvertWithFlags<double>(fflags);
+        double b_f =
+            FpConversionsTestHelper(b).ConvertWithFlags<double>(fflags);
+        ScalarRegisterType result = a_f < b_f ? 1 : 0;
+        if (std::isnan(a_f) || std::isnan(b_f)) {
+          result = 0;
+          // LT is a signaling comparison, so the invalid operation flag is
+          // set.
+          fflags |= static_cast<uint32_t>(
+              mpact::sim::riscv::FPExceptions::kInvalidOp);
+        }
+        return std::make_tuple(result, fflags);
+      });
+}
+
+// Helper to test the comparison of two half precision values for less than or
+// equal to.
+template <typename ScalarRegister>
+void RVZfhInstructionTestBase::CmpLeHelper() {
+  using ScalarRegisterType = ScalarRegister::ValueType;
+  BinaryOpWithFflagsFPTestHelper<ScalarRegisterType, HalfFP, HalfFP>(
+      "fle.h", instruction_, {"f", "f", "x"}, 32,
+      [](HalfFP a, HalfFP b) -> std::tuple<ScalarRegisterType, uint32_t> {
+        uint32_t fflags = 0;
+        double a_f =
+            FpConversionsTestHelper(a).ConvertWithFlags<double>(fflags);
+        double b_f =
+            FpConversionsTestHelper(b).ConvertWithFlags<double>(fflags);
+        ScalarRegisterType result = a_f <= b_f ? 1 : 0;
+        if (std::isnan(a_f) || std::isnan(b_f)) {
+          result = 0;
+          // LE is a signaling comparison, so the invalid operation flag is
+          // set.
+          fflags |= static_cast<uint32_t>(
+              mpact::sim::riscv::FPExceptions::kInvalidOp);
+        }
+        return std::make_tuple(result, fflags);
+      });
+}
+
+// Helper to test the classification of the half precision value.
+template <typename ScalarRegister>
+void RVZfhInstructionTestBase::ClassHelper() {
+  using ScalarRegisterType = ScalarRegister::ValueType;
+  UnaryOpWithFflagsMixedTestHelper<ScalarRegister, RVFpRegister,
+                                   ScalarRegisterType, HalfFP>(
+      "fclass.h", instruction_, {"f", "x"}, 32,
+      [](HalfFP input, int rm) -> std::tuple<ScalarRegisterType, uint32_t> {
+        uint16_t sign_mask =
+            ~(FPTypeInfo<HalfFP>::kExpMask | FPTypeInfo<HalfFP>::kSigMask);
+        uint16_t sign = input.value & sign_mask;
+        int shift = -1;
+
+        switch (input.value & FPTypeInfo<HalfFP>::kExpMask) {
+          case 0:
+            if (input.value & FPTypeInfo<HalfFP>::kSigMask) {
+              shift = sign ? 2 : 5;  // +/- Subnormal
+            } else {
+              shift = sign ? 3 : 4;  // +/- zero
+            }
+            break;
+          case FPTypeInfo<HalfFP>::kExpMask:
+            if (input.value & FPTypeInfo<HalfFP>::kSigMask) {
+              // Quiet/Signaling NaN
+              shift = FPTypeInfo<HalfFP>::IsQNaN(input) ? 9 : 8;
+            } else {
+              shift = sign ? 0 : 7;  // +/- infinity
+            }
+            break;
+          default:
+            shift = sign ? 1 : 6;  // +/- normal
+            break;
+        }
+        EXPECT_GE(shift, 0) << "The test didn't set the expected result.";
+        return std::make_tuple(static_cast<ScalarRegisterType>(1) << shift, 0);
+      });
+}
+
 class RV32ZfhInstructionTest : public RVZfhInstructionTestBase {
  protected:
   // Test conversion instructions. The instance variable semantic_function_ is
@@ -543,7 +717,7 @@
 // Test the FP16 load instruction. The semantic functions should match the isa
 // file.
 TEST_F(RV32ZfhInstructionTest, RiscVFlh) {
-  SetSemanticFunction(&RiscVILhu);
+  SetSemanticFunction(&::mpact::sim::riscv::RV32::RiscVILhu);
   SetChildInstruction();
   SetChildSemanticFunction(&RiscVZfhFlhChild);
 
@@ -557,7 +731,7 @@
 // Test the FP16 load instruction. When looking at the register contents as a
 // float, it should be NaN.
 TEST_F(RV32ZfhInstructionTest, RiscVFlh_float_nanbox) {
-  SetSemanticFunction(&RiscVILhu);
+  SetSemanticFunction(&::mpact::sim::riscv::RV32::RiscVILhu);
   SetChildInstruction();
   SetChildSemanticFunction(&RiscVZfhFlhChild);
 
@@ -571,7 +745,7 @@
 // Test the FP16 load instruction. When looking at the register contents as a
 // double, it should be NaN.
 TEST_F(RV32ZfhInstructionTest, RiscVFlh_double_nanbox) {
-  SetSemanticFunction(&RiscVILhu);
+  SetSemanticFunction(&::mpact::sim::riscv::RV32::RiscVILhu);
   SetChildInstruction();
   SetChildSemanticFunction(&RiscVZfhFlhChild);
 
@@ -586,14 +760,7 @@
 // encoding is preserved in the integer register.
 TEST_F(RV32ZfhInstructionTest, 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));
-        // Fill the upper XLEN-16 bits with the sign bit as per the spec.
-        uint32_t result = sign ? 0xFFFF'0000 : 0;
-        result |= static_cast<uint32_t>(half_fp.value);
-        return result;
-      });
+  FmvXhHelper<RV32Register>();
 }
 
 // Move half precision from an integer register (lower 16 bits) to a float
@@ -610,14 +777,14 @@
 // that the destination value is NaN boxed.
 TEST_F(RV32ZfhInstructionTest, RiscVZfhFMvhx_float_nanbox) {
   SetSemanticFunction(&::mpact::sim::riscv::RV32::RiscVZfhFMvhx);
-  FmvHxHelper<RV32Register, float>();
+  FmvHxNanBoxHelper<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>();
+  FmvHxNanBoxHelper<RV32Register, double>();
 }
 
 // Half precision to single precision conversion.
@@ -1157,182 +1324,49 @@
 // Test conversion from signed 32 bit integer to half precision.
 TEST_F(RV32ZfhInstructionTest, RiscVZfhCvtHw) {
   SetSemanticFunction(&RiscVZfhCvtHw);
-  UnaryOpWithFflagsMixedTestHelper<RVFpRegister, RV32Register, HalfFP, int32_t>(
-      "fcvt.h.w", instruction_, {"x", "f"}, 32,
-      [](int32_t input_int, int rm) -> std::tuple<HalfFP, uint32_t> {
-        uint32_t fflags = 0;
-        HalfFP result = FpConversionsTestHelper(static_cast<double>(input_int))
-                            .ConvertWithFlags<HalfFP>(
-                                fflags, static_cast<FPRoundingMode>(rm));
-        return std::make_tuple(result, fflags);
-      });
-}
-
-// Test conversion from half precision to signed 32 bit integer.
-TEST_F(RV32ZfhInstructionTest, RiscVZfhCvtWh) {
-  SetSemanticFunction(&RiscVZfhCvtWh);
-  UnaryOpWithFflagsMixedTestHelper<RV32Register, RVFpRegister, int32_t, HalfFP>(
-      "fcvt.w.h", instruction_, {"f", "x"}, 32,
-      [this](HalfFP input, int rm) -> std::tuple<int32_t, uint32_t> {
-        uint32_t fflags = 0;
-        double input_double =
-            FpConversionsTestHelper(input).ConvertWithFlags<double>(fflags);
-        const int32_t val =
-            RoundToInteger<double, int32_t>(input_double, rm, fflags);
-        return std::make_tuple(val, fflags);
-      });
+  CvtHwHelper<RV32Register, int32_t>("fcvt.h.w");
 }
 
 // Test conversion from unsigned 32 bit integer to half precision.
 TEST_F(RV32ZfhInstructionTest, RiscVZfhCvtHwu) {
   SetSemanticFunction(&RiscVZfhCvtHwu);
-  UnaryOpWithFflagsMixedTestHelper<RVFpRegister, RV32Register, HalfFP,
-                                   uint32_t>(
-      "fcvt.h.wu", instruction_, {"x", "f"}, 32,
-      [](uint32_t input_int, int rm) -> std::tuple<HalfFP, uint32_t> {
-        uint32_t fflags = 0;
-        HalfFP result = FpConversionsTestHelper(static_cast<double>(input_int))
-                            .ConvertWithFlags<HalfFP>(
-                                fflags, static_cast<FPRoundingMode>(rm));
-        return std::make_tuple(result, fflags);
-      });
+  CvtHwHelper<RV32Register, uint32_t>("fcvt.h.wu");
+}
+
+// Test conversion from half precision to signed 32 bit integer.
+TEST_F(RV32ZfhInstructionTest, RiscVZfhCvtWh) {
+  SetSemanticFunction(&::mpact::sim::riscv::RV32::RiscVZfhCvtWh);
+  CvtWhHelper<RV32Register, int32_t>("fcvt.w.h");
 }
 
 // Test conversion from half precision to unsigned 32 bit integer.
 TEST_F(RV32ZfhInstructionTest, RiscVZfhCvtWuh) {
-  SetSemanticFunction(&RiscVZfhCvtWuh);
-  UnaryOpWithFflagsMixedTestHelper<RV32Register, RVFpRegister, uint32_t,
-                                   HalfFP>(
-      "fcvt.wu.h", instruction_, {"f", "x"}, 32,
-      [this](HalfFP input, int rm) -> std::tuple<uint32_t, uint32_t> {
-        uint32_t fflags = 0;
-        double input_double =
-            FpConversionsTestHelper(input).ConvertWithFlags<double>(fflags);
-        const uint32_t val =
-            RoundToInteger<double, uint32_t>(input_double, rm, fflags);
-        return std::make_tuple(val, fflags);
-      });
+  SetSemanticFunction(&::mpact::sim::riscv::RV32::RiscVZfhCvtWuh);
+  CvtWhHelper<RV32Register, uint32_t>("fcvt.wu.h");
 }
 
 // Test equality comparison for half precision values.
 TEST_F(RV32ZfhInstructionTest, RiscVZfhFcmpeq) {
-  SetSemanticFunction(&RiscVZfhFcmpeq);
-  BinaryOpWithFflagsFPTestHelper<uint64_t, HalfFP, HalfFP>(
-      "feq.h", instruction_, {"f", "f", "x"}, 32,
-      [](HalfFP a, HalfFP b) -> std::tuple<uint64_t, uint32_t> {
-        uint32_t fflags = 0;
-        double a_f =
-            FpConversionsTestHelper(a).ConvertWithFlags<double>(fflags);
-        double b_f =
-            FpConversionsTestHelper(b).ConvertWithFlags<double>(fflags);
-        uint64_t result = a_f == b_f ? 1 : 0;
-        if (std::isnan(a_f) || std::isnan(b_f)) {
-          result = 0;
-        }
-        return std::make_tuple(result, fflags);
-      });
+  SetSemanticFunction(&::mpact::sim::riscv::RV32::RiscVZfhFcmpeq);
+  CmpEqHelper<RV32Register>();
 }
 
 // Test less than comparison for half precision values.
 TEST_F(RV32ZfhInstructionTest, RiscVZfhFcmplt) {
-  SetSemanticFunction(&RiscVZfhFcmplt);
-  BinaryOpWithFflagsFPTestHelper<uint64_t, HalfFP, HalfFP>(
-      "flt.h", instruction_, {"f", "f", "x"}, 32,
-      [](HalfFP a, HalfFP b) -> std::tuple<uint64_t, uint32_t> {
-        uint32_t fflags = 0;
-        double a_f =
-            FpConversionsTestHelper(a).ConvertWithFlags<double>(fflags);
-        double b_f =
-            FpConversionsTestHelper(b).ConvertWithFlags<double>(fflags);
-        uint64_t result = a_f < b_f ? 1 : 0;
-        if (std::isnan(a_f) || std::isnan(b_f)) {
-          result = 0;
-          // LT is a signaling comparison, so the invalid operation flag is
-          // set.
-          fflags |= static_cast<uint32_t>(
-              mpact::sim::riscv::FPExceptions::kInvalidOp);
-        }
-        return std::make_tuple(result, fflags);
-      });
+  SetSemanticFunction(&::mpact::sim::riscv::RV32::RiscVZfhFcmplt);
+  CmpLtHelper<RV32Register>();
 }
 
 // Test less than or equal to comparison for half precision values.
 TEST_F(RV32ZfhInstructionTest, RiscVZfhFcmple) {
-  SetSemanticFunction(&RiscVZfhFcmple);
-  BinaryOpWithFflagsFPTestHelper<uint64_t, HalfFP, HalfFP>(
-      "fle.h", instruction_, {"f", "f", "x"}, 32,
-      [](HalfFP a, HalfFP b) -> std::tuple<uint64_t, uint32_t> {
-        uint32_t fflags = 0;
-        double a_f =
-            FpConversionsTestHelper(a).ConvertWithFlags<double>(fflags);
-        double b_f =
-            FpConversionsTestHelper(b).ConvertWithFlags<double>(fflags);
-        uint64_t result = a_f <= b_f ? 1 : 0;
-        if (std::isnan(a_f) || std::isnan(b_f)) {
-          result = 0;
-          // LE is a signaling comparison, so the invalid operation flag is
-          // set.
-          fflags |= static_cast<uint32_t>(
-              mpact::sim::riscv::FPExceptions::kInvalidOp);
-        }
-        return std::make_tuple(result, fflags);
-      });
+  SetSemanticFunction(&::mpact::sim::riscv::RV32::RiscVZfhFcmple);
+  CmpLeHelper<RV32Register>();
 }
 
 // Test classification of half precision values.
 TEST_F(RV32ZfhInstructionTest, RiscVZfhFclass) {
-  SetSemanticFunction(&RiscVZfhFclass);
-  UnaryOpWithFflagsMixedTestHelper<RV32Register, RVFpRegister, uint32_t,
-                                   HalfFP>(
-      "fclass.h", instruction_, {"f", "x"}, 32,
-      [](HalfFP input, int rm) -> std::tuple<uint32_t, uint32_t> {
-        uint16_t sign_mask =
-            ~(FPTypeInfo<HalfFP>::kExpMask | FPTypeInfo<HalfFP>::kSigMask);
-        uint16_t sign = input.value & sign_mask;
-        int shift = -1;
-
-        switch (input.value & FPTypeInfo<HalfFP>::kExpMask) {
-          case 0:
-            if (input.value & FPTypeInfo<HalfFP>::kSigMask) {
-              if (sign) {
-                shift = 2;  // Negative subnormal
-              } else {
-                shift = 5;  // Positive subnormal
-              }
-            } else {
-              if (sign) {
-                shift = 3;  // Negative zero
-              } else {
-                shift = 4;  // Positive zero
-              }
-            }
-            break;
-          case FPTypeInfo<HalfFP>::kExpMask:
-            if (input.value & FPTypeInfo<HalfFP>::kSigMask) {
-              if (FPTypeInfo<HalfFP>::IsQNaN(input)) {
-                shift = 9;  // Quiet NaN
-              } else {
-                shift = 8;  // Signaling NaN
-              }
-            } else {  // Inf
-              if (sign) {
-                shift = 0;  // Negative infinity
-              } else {
-                shift = 7;  // Positive infinity
-              }
-            }
-            break;
-          default:
-            if (sign) {
-              shift = 1;  // Negative normal
-            } else {
-              shift = 6;  // Positive normal
-            }
-            break;
-        }
-        EXPECT_GE(shift, 0) << "The test didn't set the expected result.";
-        return std::make_tuple(1 << shift, 0);
-      });
+  SetSemanticFunction(&::mpact::sim::riscv::RV32::RiscVZfhFclass);
+  ClassHelper<RV32Register>();
 }
 
 // Test fused multiply add for half precision values.
@@ -1481,14 +1515,7 @@
 // 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;
-      });
+  FmvXhHelper<RV64Register>();
 }
 
 // Move half precision from an integer register (lower 16 bits) to a float
@@ -1505,20 +1532,20 @@
 // that the destination value is NaN boxed.
 TEST_F(RV64ZfhInstructionTest, RiscVZfhFMvhx_float_nanbox) {
   SetSemanticFunction(&::mpact::sim::riscv::RV64::RiscVZfhFMvhx);
-  FmvHxHelper<RV64Register, float>();
+  FmvHxNanBoxHelper<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>();
+  FmvHxNanBoxHelper<RV64Register, double>();
 }
 
 // Test the FP16 load instruction. The semantic functions should match the isa
 // file.
 TEST_F(RV64ZfhInstructionTest, RiscVFlh) {
-  SetSemanticFunction(&RiscVILhu);
+  SetSemanticFunction(&::mpact::sim::riscv::RV64::RiscVILhu);
   SetChildInstruction();
   SetChildSemanticFunction(&RiscVZfhFlhChild);
 
@@ -1532,7 +1559,7 @@
 // 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);
+  SetSemanticFunction(&::mpact::sim::riscv::RV64::RiscVILhu);
   SetChildInstruction();
   SetChildSemanticFunction(&RiscVZfhFlhChild);
 
@@ -1546,7 +1573,7 @@
 // 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);
+  SetSemanticFunction(&::mpact::sim::riscv::RV64::RiscVILhu);
   SetChildInstruction();
   SetChildSemanticFunction(&RiscVZfhFlhChild);
 
@@ -1557,4 +1584,52 @@
   EXPECT_TRUE(std::isnan(observed_val));
 }
 
+// Test conversion from signed 32 bit integer to half precision.
+TEST_F(RV64ZfhInstructionTest, RiscVZfhCvtHw) {
+  SetSemanticFunction(&RiscVZfhCvtHw);
+  CvtHwHelper<RV64Register, int32_t>("fcvt.h.w");
+}
+
+// Test conversion from unsigned 32 bit integer to half precision.
+TEST_F(RV64ZfhInstructionTest, RiscVZfhCvtHwu) {
+  SetSemanticFunction(&RiscVZfhCvtHwu);
+  CvtHwHelper<RV64Register, uint32_t>("fcvt.h.wu");
+}
+
+// Test conversion from half precision to signed 32 bit integer.
+TEST_F(RV64ZfhInstructionTest, RiscVZfhCvtWh) {
+  SetSemanticFunction(&::mpact::sim::riscv::RV64::RiscVZfhCvtWh);
+  CvtWhHelper<RV64Register, int32_t>("fcvt.w.h");
+}
+
+// Test conversion from half precision to unsigned 32 bit integer.
+TEST_F(RV64ZfhInstructionTest, RiscVZfhCvtWuh) {
+  SetSemanticFunction(&::mpact::sim::riscv::RV64::RiscVZfhCvtWuh);
+  CvtWhHelper<RV64Register, uint32_t>("fcvt.wu.h");
+}
+
+// Test equality comparison for half precision values.
+TEST_F(RV64ZfhInstructionTest, RiscVZfhFcmpeq) {
+  SetSemanticFunction(&::mpact::sim::riscv::RV64::RiscVZfhFcmpeq);
+  CmpEqHelper<RV64Register>();
+}
+
+// Test less than comparison for half precision values.
+TEST_F(RV64ZfhInstructionTest, RiscVZfhFcmplt) {
+  SetSemanticFunction(&::mpact::sim::riscv::RV64::RiscVZfhFcmplt);
+  CmpLtHelper<RV64Register>();
+}
+
+// Test less than or equal to comparison for half precision values.
+TEST_F(RV64ZfhInstructionTest, RiscVZfhFcmple) {
+  SetSemanticFunction(&::mpact::sim::riscv::RV64::RiscVZfhFcmple);
+  CmpLeHelper<RV64Register>();
+}
+
+// Test classification of half precision values.
+TEST_F(RV64ZfhInstructionTest, RiscVZfhFclass) {
+  SetSemanticFunction(&::mpact::sim::riscv::RV64::RiscVZfhFclass);
+  ClassHelper<RV64Register>();
+}
+
 }  // namespace