Binary decoder for 32 bit riscv Zfhmin

PiperOrigin-RevId: 752427121
Change-Id: I3d854d8e3f57e16157707b1acf26bb538a20df03
diff --git a/riscv/BUILD b/riscv/BUILD
index 4983ea6..cfc03dc 100644
--- a/riscv/BUILD
+++ b/riscv/BUILD
@@ -697,6 +697,32 @@
     ],
 )
 
+mpact_isa_decoder(
+    name = "zfh_isa",
+    src = "riscv_zfh.isa",
+    includes = [],
+    isa_name = "ZFH",
+    prefix = "zfh",
+    deps = [
+        ":riscv_g",
+        ":riscv_zfh_instructions",
+        "@com_google_absl//absl/functional:bind_front",
+    ],
+)
+
+mpact_bin_fmt_decoder(
+    name = "zfh_bin_fmt",
+    src = "riscv_zfh.bin_fmt",
+    decoder_name = "ZFH",
+    includes = [
+        "riscv32g.bin_fmt",
+    ],
+    prefix = "zfh",
+    deps = [
+        ":zfh_isa",
+    ],
+)
+
 cc_library(
     name = "riscv32g_decoder",
     srcs = [
diff --git a/riscv/riscv_zfh.bin_fmt b/riscv/riscv_zfh.bin_fmt
new file mode 100644
index 0000000..f0b48ff
--- /dev/null
+++ b/riscv/riscv_zfh.bin_fmt
@@ -0,0 +1,35 @@
+// Copyright 2025 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "riscv/riscv32g.bin_fmt"
+
+decoder ZFH {
+  namespace mpact::sim::riscv::zfh;
+  opcode_enum = "OpcodeEnum";
+  includes {
+    #include "riscv/zfh_decoder.h"
+  }
+  RiscVZfhInst32 = { RiscVZfhminInst32 };
+}
+
+instruction group RiscVZfhminInst32[32] : Inst32Format {
+  flh     : IType : func3 == 0b001,  opcode == 0b000'0111;
+  fsh     : SType : func3 == 0b001,  opcode == 0b010'0111;
+  fmv_xh  : RType : func7 == 0b1110010, rs2 == 0, func3 == 0b000, opcode == 0b1010011;
+  fmv_hx  : RType : func7 == 0b1111010, rs2 == 0, func3 == 0b000, opcode == 0b1010011;
+  fcvt_sh : RType : func7 == 0b0100000, rs2 == 0b00010, opcode == 0b1010011;
+  fcvt_hs : RType : func7 == 0b0100010, rs2 == 0b00000, opcode == 0b1010011;
+  fcvt_dh : RType : func7 == 0b0100001, rs2 == 0b00010, opcode == 0b1010011;
+  fcvt_hd : RType : func7 == 0b0100010, rs2 == 0b00001, opcode == 0b1010011;
+};
diff --git a/riscv/test/BUILD b/riscv/test/BUILD
index da3f7e6..fd65642 100644
--- a/riscv/test/BUILD
+++ b/riscv/test/BUILD
@@ -378,6 +378,50 @@
     ],
 )
 
+cc_library(
+    name = "zfh_decoder",
+    testonly = True,
+    srcs = [
+        "zfh_encoding.cc",
+    ],
+    hdrs = [
+        "riscv_getters_zfh.h",
+        "zfh_encoding.h",
+    ],
+    copts = ["-O3"],
+    deps = [
+        "//riscv:riscv_encoding_common",
+        "//riscv:riscv_getters",
+        "//riscv:riscv_state",
+        "//riscv:zfh_bin_fmt",
+        "//riscv:zfh_isa",
+        "@com_google_absl//absl/log",
+        "@com_google_absl//absl/strings",
+        "@com_google_mpact-sim//mpact/sim/generic:core",
+        "@com_google_mpact-sim//mpact/sim/generic:type_helpers",
+    ],
+)
+
+cc_test(
+    name = "zfh_encoding_test",
+    size = "small",
+    srcs = [
+        "zfh_encoding_test.cc",
+    ],
+    deps = [
+        ":zfh_decoder",
+        "//riscv:riscv_state",
+        "//riscv:zfh_isa",
+        "@com_google_absl//absl/random",
+        "@com_google_absl//absl/strings",
+        "@com_google_googletest//:gtest_main",
+        "@com_google_mpact-sim//mpact/sim/generic:arch_state",
+        "@com_google_mpact-sim//mpact/sim/generic:core",
+        "@com_google_mpact-sim//mpact/sim/generic:type_helpers",
+        "@com_google_mpact-sim//mpact/sim/util/memory",
+    ],
+)
+
 cc_test(
     name = "riscv32_htif_semihost_test",
     size = "small",
diff --git a/riscv/test/riscv_getters_zfh.h b/riscv/test/riscv_getters_zfh.h
new file mode 100644
index 0000000..bdb926c
--- /dev/null
+++ b/riscv/test/riscv_getters_zfh.h
@@ -0,0 +1,129 @@
+// Copyright 2025 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef THIRD_PARTY_MPACT_RISCV_TEST_RISCV_GETTERS_ZFH_H_
+#define THIRD_PARTY_MPACT_RISCV_TEST_RISCV_GETTERS_ZFH_H_
+
+#include <cstdint>
+#include <string>
+
+#include "absl/strings/str_cat.h"
+#include "mpact/sim/generic/immediate_operand.h"
+#include "mpact/sim/generic/literal_operand.h"
+#include "mpact/sim/generic/type_helpers.h"
+#include "riscv/riscv_encoding_common.h"
+#include "riscv/riscv_getter_helpers.h"
+#include "riscv/riscv_getters_vector.h"
+#include "riscv/riscv_register.h"
+#include "riscv/riscv_register_aliases.h"
+
+namespace mpact {
+namespace sim {
+namespace riscv {
+
+using ::mpact::sim::generic::operator*;  // NOLINT: is used below (clang error).
+
+template <typename Enum, typename Extractors, typename IntegerRegister>
+void AddRiscVZfhSourceScalarGetters(SourceOpGetterMap &getter_map,
+                                    RiscVEncodingCommon *common) {
+  // Source operand getters.
+  Insert(getter_map, *Enum::kRs1, [common]() -> SourceOperandInterface * {
+    int num = Extractors::Inst32Format::ExtractRs1(common->inst_word());
+    if (num == 0) return new generic::IntLiteralOperand<0>({1});
+    return GetRegisterSourceOp<IntegerRegister>(
+        common->state(), absl::StrCat(RiscVState::kXregPrefix, num),
+        kXRegisterAliases[num]);
+  });
+  // I in IImm12 stands for IType instructions.
+  Insert(getter_map, *Enum::kIImm12, [common]() -> SourceOperandInterface * {
+    const auto num = Extractors::IType::ExtractImm12(common->inst_word());
+    return new generic::ImmediateOperand<int32_t>(num);
+  });
+  // S in SImm12 stands for SType instructions.
+  Insert(getter_map, *Enum::kSImm12, [common]() -> SourceOperandInterface * {
+    const auto num = Extractors::SType::ExtractSImm(common->inst_word());
+    return new generic::ImmediateOperand<int32_t>(num);
+  });
+  Insert(getter_map, *Enum::kRm, [common]() -> SourceOperandInterface * {
+    const auto num =
+        Extractors::Inst32Format::ExtractFunc3(common->inst_word());
+    return new generic::ImmediateOperand<uint8_t>(num);
+  });
+}
+
+template <typename Enum, typename Extractors, typename FloatRegister>
+void AddRiscVZfhSourceFloatGetters(SourceOpGetterMap &getter_map,
+                                   RiscVEncodingCommon *common) {
+  // Source operand getters.
+  Insert(getter_map, *Enum::kFrs1, [common]() -> SourceOperandInterface * {
+    int num = Extractors::Inst32Format::ExtractRs1(common->inst_word());
+    return GetRegisterSourceOp<FloatRegister>(
+        common->state(), absl::StrCat(RiscVState::kFregPrefix, num),
+        kFRegisterAliases[num]);
+  });
+  Insert(getter_map, *Enum::kFrs2, [common]() -> SourceOperandInterface * {
+    int num = Extractors::Inst32Format::ExtractRs2(common->inst_word());
+    return GetRegisterSourceOp<FloatRegister>(
+        common->state(), absl::StrCat(RiscVState::kFregPrefix, num),
+        kFRegisterAliases[num]);
+  });
+}
+
+template <typename Enum, typename Extractors, typename IntegerRegister>
+void AddRiscVZfhDestScalarGetters(DestOpGetterMap &getter_map,
+                                  RiscVEncodingCommon *common) {
+  // Destination operand getters.
+  Insert(getter_map, *Enum::kRd,
+         [common](int latency) -> DestinationOperandInterface * {
+           auto num = Extractors::Inst32Format::ExtractRd(common->inst_word());
+           std::string name = absl::StrCat(RiscVState::kXregPrefix, num);
+           return mpact::sim::riscv::GetRegisterDestinationOp<IntegerRegister>(
+               common->state(), name, latency);
+         });
+  Insert(getter_map, *Enum::kFflags,
+         [common](int latency) -> DestinationOperandInterface * {
+           return GetCSRSetBitsDestinationOp<uint32_t>(common->state(),
+                                                       "fflags", latency, "");
+         });
+}
+
+template <typename Enum, typename Extractors, typename FloatRegister>
+void AddRiscVZfhDestFloatGetters(DestOpGetterMap &getter_map,
+                                 RiscVEncodingCommon *common) {
+  // Destination operand getters.
+  Insert(getter_map, *Enum::kFrd,
+         [common](int latency) -> DestinationOperandInterface * {
+           auto num = Extractors::Inst32Format::ExtractRd(common->inst_word());
+           std::string name = absl::StrCat(RiscVState::kFregPrefix, num);
+           return mpact::sim::riscv::GetRegisterDestinationOp<FloatRegister>(
+               common->state(), name, latency);
+         });
+}
+
+// This function is used to add the simple resource getters to the given
+// "getter map". The function is templated on the enum type that defines the
+// simple resource types, the Extractors type that defines the bit
+// extraction functions, and the IntRegister and FpRegister types that are used
+// to construct the register operand.
+template <typename Enum, typename Extractors>
+void AddRiscVZfhSimpleResourceGetters(SimpleResourceGetterMap &getter_map,
+                                      RiscVEncodingCommon *common) {
+  // TODO(julianmb): Add resource getters when appropriate.
+}
+
+}  // namespace riscv
+}  // namespace sim
+}  // namespace mpact
+
+#endif  // THIRD_PARTY_MPACT_RISCV_TEST_RISCV_GETTERS_ZFH_H_
diff --git a/riscv/test/zfh_encoding.cc b/riscv/test/zfh_encoding.cc
new file mode 100644
index 0000000..7be9df3
--- /dev/null
+++ b/riscv/test/zfh_encoding.cc
@@ -0,0 +1,132 @@
+// Copyright 2025 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "riscv/test/zfh_encoding.h"
+
+#include <cstdint>
+
+#include "absl/log/log.h"
+#include "absl/strings/str_cat.h"
+#include "mpact/sim/generic/simple_resource_operand.h"
+#include "mpact/sim/generic/type_helpers.h"
+#include "riscv/riscv_getter_helpers.h"
+#include "riscv/riscv_register.h"
+#include "riscv/riscv_state.h"
+#include "riscv/test/riscv_getters_zfh.h"
+#include "riscv/zfh_bin_decoder.h"
+#include "riscv/zfh_decoder.h"
+#include "riscv/zfh_enums.h"
+
+namespace mpact::sim::riscv::zfh {
+
+using ::mpact::sim::generic::operator*;  // NOLINT: clang-tidy false positive.
+
+ZFHEncoding::ZFHEncoding(RiscVState *state)
+    : state_(state),
+      inst_word_(0),
+      opcode_(OpcodeEnum::kNone),
+      format_(FormatEnum::kNone) {
+  resource_delay_line_ =
+      state_->CreateAndAddDelayLine<generic::SimpleResourceDelayLine>(8);
+  // Initialize getters.
+  source_op_getters_.emplace(*SourceOpEnum::kNone, []() { return nullptr; });
+  dest_op_getters_.emplace(*DestOpEnum::kNone,
+                           [](int latency) { return nullptr; });
+  simple_resource_getters_.emplace(*SimpleResourceEnum::kNone,
+                                   []() { return nullptr; });
+  complex_resource_getters_.emplace(
+      *ComplexResourceEnum::kNone,
+      [](int latency, int end) { return nullptr; });
+
+  AddRiscVZfhSourceScalarGetters<SourceOpEnum, Extractors, RVXRegister>(
+      source_op_getters_, this);
+  AddRiscVZfhSourceFloatGetters<SourceOpEnum, Extractors, RVFpRegister>(
+      source_op_getters_, this);
+  AddRiscVZfhDestScalarGetters<DestOpEnum, Extractors, RVXRegister>(
+      dest_op_getters_, this);
+  AddRiscVZfhDestFloatGetters<DestOpEnum, Extractors, RVFpRegister>(
+      dest_op_getters_, this);
+  AddRiscVZfhSimpleResourceGetters<SimpleResourceEnum, Extractors>(
+      simple_resource_getters_, this);
+
+  // Verify that there are getters for each enum value.
+  for (int i = *SourceOpEnum::kNone; i < *SourceOpEnum::kPastMaxValue; ++i) {
+    if (source_op_getters_.find(i) == source_op_getters_.end()) {
+      LOG(ERROR) << "No getter for source op enum value " << i;
+    }
+  }
+  for (int i = *DestOpEnum::kNone; i < *DestOpEnum::kPastMaxValue; ++i) {
+    if (dest_op_getters_.find(i) == dest_op_getters_.end()) {
+      LOG(ERROR) << "No getter for destination op enum value " << i;
+    }
+  }
+  for (int i = *SimpleResourceEnum::kNone;
+       i < *SimpleResourceEnum::kPastMaxValue; ++i) {
+    if (simple_resource_getters_.find(i) == simple_resource_getters_.end()) {
+      LOG(ERROR) << "No getter for simple resource enum value " << i;
+    }
+  }
+}
+
+ZFHEncoding::~ZFHEncoding() { delete resource_pool_; }
+
+void ZFHEncoding::ParseInstruction(uint32_t inst_word) {
+  inst_word_ = inst_word;
+  auto [opcode, format] = DecodeRiscVZfhInst32WithFormat(inst_word_);
+  opcode_ = opcode;
+  format_ = format;
+}
+
+ResourceOperandInterface *ZFHEncoding::GetComplexResourceOperand(
+    SlotEnum, int, OpcodeEnum, ComplexResourceEnum resource, int begin,
+    int end) {
+  return nullptr;
+}
+
+ResourceOperandInterface *ZFHEncoding::GetSimpleResourceOperand(
+    SlotEnum, int, OpcodeEnum, SimpleResourceVector &resource_vec, int end) {
+  return nullptr;
+}
+
+DestinationOperandInterface *ZFHEncoding::GetDestination(SlotEnum, int,
+                                                         OpcodeEnum opcode,
+                                                         DestOpEnum dest_op,
+                                                         int dest_no,
+                                                         int latency) {
+  int index = static_cast<int>(dest_op);
+  auto iter = dest_op_getters_.find(index);
+  if (iter == dest_op_getters_.end()) {
+    LOG(ERROR) << absl::StrCat("No getter for destination op enum value ",
+                               index, "for instruction ",
+                               kOpcodeNames[static_cast<int>(opcode)]);
+    return nullptr;
+  }
+  return (iter->second)(latency);
+}
+
+SourceOperandInterface *ZFHEncoding::GetSource(SlotEnum, int, OpcodeEnum opcode,
+                                               SourceOpEnum source_op,
+                                               int source_no) {
+  int index = static_cast<int>(source_op);
+  auto iter = source_op_getters_.find(index);
+  if (iter == source_op_getters_.end()) {
+    LOG(ERROR) << absl::StrCat("No getter for source op enum value ", index,
+                               " for instruction ",
+                               kOpcodeNames[static_cast<int>(opcode)]);
+    return nullptr;
+  }
+  return (iter->second)();
+}
+
+}  // namespace mpact::sim::riscv::zfh
diff --git a/riscv/test/zfh_encoding.h b/riscv/test/zfh_encoding.h
new file mode 100644
index 0000000..9e77a56
--- /dev/null
+++ b/riscv/test/zfh_encoding.h
@@ -0,0 +1,103 @@
+// Copyright 2025 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef THIRD_PARTY_MPACT_RISCV_TEST_ZFH_ENCODING_H_
+#define THIRD_PARTY_MPACT_RISCV_TEST_ZFH_ENCODING_H_
+
+#include <cstdint>
+#include <string>
+
+#include "mpact/sim/generic/simple_resource.h"
+#include "mpact/sim/generic/simple_resource_operand.h"
+#include "riscv/riscv_encoding_common.h"
+#include "riscv/riscv_getter_helpers.h"
+#include "riscv/riscv_state.h"
+#include "riscv/zfh_bin_decoder.h"
+#include "riscv/zfh_decoder.h"
+#include "riscv/zfh_enums.h"
+
+namespace mpact::sim::riscv::zfh {
+
+// This class provides the interface between the generated instruction decoder
+// framework (which is agnostic of the actual bit representation of
+// instructions) and the instruction representation. This class provides methods
+// to return the opcode, source operands, and destination operands for
+// instructions according to the operand fields in the encoding.
+class ZFHEncoding : public ZFHEncodingBase, public RiscVEncodingCommon {
+ public:
+  explicit ZFHEncoding(RiscVState *state);
+  ~ZFHEncoding() override;
+
+  void ParseInstruction(uint32_t inst_word);
+  OpcodeEnum GetOpcode(SlotEnum, int) override { return opcode_; }
+  FormatEnum GetFormat(SlotEnum, int) { return format_; }
+
+  PredicateOperandInterface *GetPredicate(SlotEnum, int, OpcodeEnum,
+                                          PredOpEnum) override {
+    return nullptr;
+  }
+
+  ResourceOperandInterface *GetSimpleResourceOperand(
+      SlotEnum, int, OpcodeEnum, SimpleResourceVector &resource_vec,
+      int end) override;
+
+  ResourceOperandInterface *GetComplexResourceOperand(
+      SlotEnum, int, OpcodeEnum, ComplexResourceEnum resource, int begin,
+      int end) override;
+
+  SourceOperandInterface *GetSource(SlotEnum, int, OpcodeEnum, SourceOpEnum op,
+                                    int source_no) override;
+
+  DestinationOperandInterface *GetDestination(SlotEnum, int, OpcodeEnum,
+                                              DestOpEnum op, int dest_no,
+                                              int latency) override;
+
+  int GetLatency(SlotEnum, int, OpcodeEnum, DestOpEnum, int) override {
+    return 0;
+  }
+
+  // Methods inherited from RiscVEncodingCommon.
+  RiscVState *state() const override { return state_; }
+  generic::SimpleResourcePool *resource_pool() override {
+    return resource_pool_;
+  }
+  uint32_t inst_word() const override { return inst_word_; }
+
+  const SourceOpGetterMap &source_op_getters() { return source_op_getters_; }
+  const DestOpGetterMap &dest_op_getters() { return dest_op_getters_; }
+  const SimpleResourceGetterMap &simple_resource_getters() {
+    return simple_resource_getters_;
+  }
+  const ComplexResourceGetterMap &complex_resource_getters() {
+    return complex_resource_getters_;
+  }
+
+ private:
+  std::string GetSimpleResourceName(SimpleResourceEnum resource_enum);
+
+  RiscVState *state_;
+  uint32_t inst_word_;
+  OpcodeEnum opcode_;
+  FormatEnum format_;
+  SourceOpGetterMap source_op_getters_;
+  DestOpGetterMap dest_op_getters_;
+  SimpleResourceGetterMap simple_resource_getters_;
+  ComplexResourceGetterMap complex_resource_getters_;
+  generic::SimpleResourceDelayLine *resource_delay_line_ = nullptr;
+  generic::SimpleResourcePool *resource_pool_ = nullptr;
+};
+
+}  // namespace mpact::sim::riscv::zfh
+
+#endif  // THIRD_PARTY_MPACT_RISCV_TEST_ZFH_ENCODING_H_
diff --git a/riscv/test/zfh_encoding_test.cc b/riscv/test/zfh_encoding_test.cc
new file mode 100644
index 0000000..dd37b8b
--- /dev/null
+++ b/riscv/test/zfh_encoding_test.cc
@@ -0,0 +1,412 @@
+// Copyright 2025 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "riscv/test/zfh_encoding.h"
+
+#include <sys/types.h>
+
+#include <cstdint>
+#include <ios>
+#include <memory>
+#include <tuple>
+
+#include "absl/random/random.h"
+#include "absl/strings/str_cat.h"
+#include "mpact/sim/generic/immediate_operand.h"
+#include "mpact/sim/generic/operand_interface.h"
+#include "mpact/sim/generic/register.h"
+#include "mpact/sim/generic/type_helpers.h"
+#include "mpact/sim/util/memory/flat_demand_memory.h"
+#include "riscv/riscv_register.h"
+#include "riscv/riscv_state.h"
+#include "riscv/zfh_enums.h"
+#include "testing/base/public/gunit.h"
+
+// Test that hand crafted zfh instructions are decoded and parsed correctly.
+
+namespace {
+
+using ::mpact::sim::generic::operator*;  // NOLINT: clang-tidy false positive.
+using ::mpact::sim::generic::ImmediateOperand;
+using ::mpact::sim::generic::RegisterDestinationOperand;
+using ::mpact::sim::generic::SourceOperandInterface;
+using mpact::sim::riscv::RiscVState;
+using mpact::sim::riscv::RiscVXlen;
+using mpact::sim::riscv::RVFpRegister;
+using mpact::sim::riscv::RVXRegister;
+using mpact::sim::util::FlatDemandMemory;
+
+using mpact::sim::riscv::zfh::kComplexResourceNames;
+using mpact::sim::riscv::zfh::kDestOpNames;
+using mpact::sim::riscv::zfh::kSourceOpNames;
+
+using SlotEnum = mpact::sim::riscv::zfh::SlotEnum;
+using OpcodeEnum = mpact::sim::riscv::zfh::OpcodeEnum;
+using SourceOpEnum = mpact::sim::riscv::zfh::SourceOpEnum;
+using DestOpEnum = mpact::sim::riscv::zfh::DestOpEnum;
+using ComplexResourceEnum = mpact::sim::riscv::zfh::ComplexResourceEnum;
+
+using mpact::sim::riscv::zfh::ZFHEncoding;
+
+//                           imm12      | rs1 |   | rd  | opcode
+constexpr uint32_t kFlh = 0b000000000000'00000'001'00000'0000111;
+//                          imm7   | rs2 | rs1 |   |imm5 | opcode
+constexpr uint32_t kFsh = 0b0000000'00000'00000'001'00000'0100111;
+//                            func7  |     | rs1 |fn3| rd  | opcode
+constexpr uint32_t kFmvXh = 0b1110010'00000'00000'000'00000'1010011;
+//                            func7  |     | rs1 |fn3| rd  | opcode
+constexpr uint32_t kFmvHx = 0b1111010'00000'00000'000'00000'1010011;
+//                             func7  |     | rs1 |rm | rd  | opcode
+constexpr uint32_t kFcvtSh = 0b0100000'00010'00000'000'00000'1010011;
+//                             func7  |     | rs1 |rm | rd  | opcode
+constexpr uint32_t kFcvtHs = 0b0100010'00000'00000'000'00000'1010011;
+//                             func7  |     | rs1 |rm | rd  | opcode
+constexpr uint32_t kFcvtDh = 0b0100001'00010'00000'000'00000'1010011;
+//                             func7  |     | rs1 |rm | rd  | opcode
+constexpr uint32_t kFcvtHd = 0b0100010'00001'00000'000'00000'1010011;
+
+class ZfhEncodingTest : public testing::Test {
+ protected:
+  ZfhEncodingTest() {
+    state_ = new RiscVState("test", RiscVXlen::RV32, &memory_);
+    enc_ = new ZFHEncoding(state_);
+  }
+
+  ~ZfhEncodingTest() override {
+    delete enc_;
+    delete state_;
+  }
+
+  void ScalarRs1Helper(uint32_t, OpcodeEnum);
+  void FloatFrdHelper(uint32_t, OpcodeEnum);
+  void FloatSourceHelper(uint32_t, OpcodeEnum, int);
+  void FloatSourceHelper(uint32_t, OpcodeEnum, SourceOpEnum, int);
+  void FloatFrs1Helper(uint32_t, OpcodeEnum);
+  void FloatFrs2Helper(uint32_t, OpcodeEnum);
+  void FloatRmHelper(uint32_t, OpcodeEnum);
+
+  FlatDemandMemory memory_;
+  RiscVState *state_;
+  ZFHEncoding *enc_;
+  absl::BitGen gen_;
+};
+
+void ZfhEncodingTest::ScalarRs1Helper(uint32_t binary_instruction,
+                                      OpcodeEnum opcode_enum) {
+  int rs1_offset = 15;
+  uint32_t base_instruction = binary_instruction & ~(0x0000'001F << rs1_offset);
+  for (int rs1_index = 0; rs1_index < 32; ++rs1_index) {
+    uint32_t expected_value = rs1_index ? absl::Uniform<uint32_t>(gen_) : 0;
+
+    // Set the register value with a test value.
+    RVXRegister *rs1_reg;
+    std::tie(rs1_reg, std::ignore) = state_->GetRegister<RVXRegister>(
+        absl::StrCat("x", static_cast<uint32_t>(rs1_index)));
+    rs1_reg->data_buffer()->Set<uint32_t>(0, expected_value);
+
+    // Parse the instruction and get the source operand.
+    uint32_t rs1_adjustment = static_cast<uint32_t>(rs1_index) << rs1_offset;
+    enc_->ParseInstruction(base_instruction | rs1_adjustment);
+    std::unique_ptr<SourceOperandInterface> src(enc_->GetSource(
+        SlotEnum::kRiscv32ZfhMin, 0, opcode_enum, SourceOpEnum::kRs1, 0));
+
+    // Pull the value from the source operand and compare it to the expected
+    // value.
+    EXPECT_EQ(src->AsUint32(0), expected_value)
+        << "rs1_index: " << rs1_index << ", expected_value: " << std::hex
+        << expected_value << ", observed value: " << std::hex
+        << src->AsUint32(0);
+  }
+}
+
+void ZfhEncodingTest::FloatFrdHelper(uint32_t binary_instruction,
+                                     OpcodeEnum opcode_enum) {
+  int frd_offset = 7;
+  uint32_t base_instruction = binary_instruction & ~(0x0000'001F << frd_offset);
+  for (int frd_index = 0; frd_index < 32; ++frd_index) {
+    uint64_t expected_value = absl::Uniform<uint64_t>(gen_);
+    uint32_t frd_adjustment = static_cast<uint32_t>(frd_index) << frd_offset;
+
+    // Set the register value with a test value.
+    RVFpRegister *frd_reg;
+    std::tie(frd_reg, std::ignore) =
+        state_->GetRegister<RVFpRegister>(absl::StrCat("f", frd_index));
+    frd_reg->data_buffer()->Set<uint64_t>(0, expected_value);
+
+    // Parse the instruction and get the destination operand.
+    enc_->ParseInstruction(base_instruction | frd_adjustment);
+    std::unique_ptr<RegisterDestinationOperand<RVFpRegister>> dst(
+        static_cast<RegisterDestinationOperand<RVFpRegister> *>(
+            enc_->GetDestination(SlotEnum::kRiscv32ZfhMin, 0, opcode_enum,
+                                 DestOpEnum::kFrd, 0, 0)));
+
+    // Pull the value from the destination operand and compare it to the
+    // expected value.
+    uint64_t observed_value =
+        dst->GetRegister()->data_buffer()->Get<uint64_t>(0);
+    EXPECT_EQ(observed_value, expected_value)
+        << "frd_index: " << frd_index << ", expected_value: " << std::hex
+        << expected_value << ", observed value: " << std::hex << observed_value;
+  }
+}
+
+void ZfhEncodingTest::FloatSourceHelper(uint32_t binary_instruction,
+                                        OpcodeEnum opcode_enum,
+                                        SourceOpEnum source_op_enum,
+                                        int offset) {
+  uint32_t base_instruction = binary_instruction & ~(0x0000'001F << offset);
+  for (uint32_t frs1_index = 0; frs1_index < 32; ++frs1_index) {
+    uint32_t src_adjustment = frs1_index << offset;
+    uint64_t expected_value = absl::Uniform<uint64_t>(gen_);
+
+    // Set the register value with a test value.
+    RVFpRegister *frs1_reg;
+    std::tie(frs1_reg, std::ignore) =
+        state_->GetRegister<RVFpRegister>(absl::StrCat("f", frs1_index));
+    frs1_reg->data_buffer()->Set<uint64_t>(0, expected_value);
+
+    // Parse the instruction and get the source operand.
+    enc_->ParseInstruction(base_instruction | src_adjustment);
+    std::unique_ptr<SourceOperandInterface> src(enc_->GetSource(
+        SlotEnum::kRiscv32ZfhMin, 0, opcode_enum, source_op_enum, 0));
+
+    // Pull the value from the source operand and compare it to the expected
+    // value.
+    EXPECT_EQ(src->AsUint64(0), expected_value)
+        << "frs1_index: " << frs1_index << ", expected_value: " << std::hex
+        << expected_value << ", observed value: " << std::hex
+        << src->AsUint64(0);
+  }
+}
+
+void ZfhEncodingTest::FloatFrs1Helper(uint32_t binary_instruction,
+                                      OpcodeEnum opcode_enum) {
+  FloatSourceHelper(binary_instruction, opcode_enum, SourceOpEnum::kFrs1, 15);
+}
+
+void ZfhEncodingTest::FloatFrs2Helper(uint32_t binary_instruction,
+                                      OpcodeEnum opcode_enum) {
+  FloatSourceHelper(binary_instruction, opcode_enum, SourceOpEnum::kFrs2, 20);
+}
+
+void ZfhEncodingTest::FloatRmHelper(uint32_t binary_instruction,
+                                    OpcodeEnum opcode_enum) {
+  for (int rm = 0; rm <= 6; ++rm) {
+    uint32_t rm_adjustment = rm << 12;
+    enc_->ParseInstruction(kFcvtSh | rm_adjustment);
+    std::unique_ptr<SourceOperandInterface> src(
+        enc_->GetSource(SlotEnum::kRiscv32ZfhMin, 0, OpcodeEnum::kFcvtSh,
+                        SourceOpEnum::kRm, 0));
+    EXPECT_EQ(src->AsUint32(0), rm);
+  }
+}
+
+TEST_F(ZfhEncodingTest, SourceOperands) {
+  auto &getters = enc_->source_op_getters();
+  for (int i = *SourceOpEnum::kNone; i < *SourceOpEnum::kPastMaxValue; ++i) {
+    EXPECT_TRUE(getters.contains(i)) << "No source operand for enum value " << i
+                                     << " (" << kSourceOpNames[i] << ")";
+  }
+}
+
+TEST_F(ZfhEncodingTest, DestOperands) {
+  auto &getters = enc_->dest_op_getters();
+  for (int i = *DestOpEnum::kNone; i < *DestOpEnum::kPastMaxValue; ++i) {
+    EXPECT_TRUE(getters.contains(i)) << "No dest operand for enum value " << i
+                                     << " (" << kDestOpNames[i] << ")";
+  }
+}
+
+// TODO(julianmb): Add simple resource getters when appropriate.
+// TEST_F(ZfhEncodingTest, SimpleResources) {
+//   auto &getters = enc_->simple_resource_getters();
+//   for (int i = *SimpleResourceEnum::kNone;
+//        i < *SimpleResourceEnum::kPastMaxValue; ++i) {
+//     EXPECT_TRUE(getters.contains(i)) << "No source operand for enum value "
+//     << i
+//                                      << " (" << kSimpleResourceNames[i] <<
+//                                      ")";
+//   }
+// }
+
+TEST_F(ZfhEncodingTest, ComplexResources) {
+  auto &getters = enc_->source_op_getters();
+  for (int i = *ComplexResourceEnum::kNone;
+       i < *ComplexResourceEnum::kPastMaxValue; ++i) {
+    EXPECT_TRUE(getters.contains(i)) << "No source operand for enum value " << i
+                                     << " (" << kComplexResourceNames[i] << ")";
+  }
+}
+
+TEST_F(ZfhEncodingTest, Flh) {
+  enc_->ParseInstruction(kFlh);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32ZfhMin, 0), OpcodeEnum::kFlh);
+}
+
+TEST_F(ZfhEncodingTest, Flh_imm12) {
+  for (int iter = 0; iter < 1000; ++iter) {
+    int32_t expected_imm =
+        absl::Uniform<int32_t>(gen_, -1 * (1 << 11), 1 << 11);
+    bool sign = expected_imm < 0;
+    uint32_t imm_adjustment =
+        (sign ? 0x8000'0000 : 0) | ((expected_imm & 0x0000'07FF) << 20);
+    enc_->ParseInstruction(kFlh | imm_adjustment);
+    std::unique_ptr<ImmediateOperand<int32_t>> src(
+        static_cast<ImmediateOperand<int32_t> *>(
+            enc_->GetSource(SlotEnum::kRiscv32ZfhMin, 0, OpcodeEnum::kFlh,
+                            SourceOpEnum::kIImm12, 0)));
+    EXPECT_EQ(src->AsInt32(0), expected_imm);
+  }
+}
+
+TEST_F(ZfhEncodingTest, Flh_rs1) { ScalarRs1Helper(kFlh, OpcodeEnum::kFlh); }
+
+TEST_F(ZfhEncodingTest, Flh_frd) { FloatFrdHelper(kFlh, OpcodeEnum::kFlh); }
+
+TEST_F(ZfhEncodingTest, Fsh) {
+  enc_->ParseInstruction(kFsh);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32ZfhMin, 0), OpcodeEnum::kFsh);
+}
+
+TEST_F(ZfhEncodingTest, Fsh_imm12) {
+  for (int iter = 0; iter < 1000; ++iter) {
+    int32_t expected_imm =
+        absl::Uniform<int32_t>(gen_, -1 * (1 << 11), 1 << 11);
+    bool sign = expected_imm < 0;
+    uint32_t imm_adjustment = (sign ? 0x8000'0000 : 0) |
+                              ((expected_imm & 0x0000'001F) << 7) |
+                              ((expected_imm & 0x0000'07E0) << 20);
+    enc_->ParseInstruction(kFsh | imm_adjustment);
+    std::unique_ptr<ImmediateOperand<int32_t>> src(
+        static_cast<ImmediateOperand<int32_t> *>(
+            enc_->GetSource(SlotEnum::kRiscv32ZfhMin, 0, OpcodeEnum::kFsh,
+                            SourceOpEnum::kSImm12, 0)));
+    EXPECT_EQ(src->AsInt32(0), expected_imm);
+  }
+}
+
+TEST_F(ZfhEncodingTest, Fsh_rs1) { ScalarRs1Helper(kFsh, OpcodeEnum::kFsh); }
+
+TEST_F(ZfhEncodingTest, Fsh_frs2) { FloatFrs2Helper(kFsh, OpcodeEnum::kFsh); }
+
+TEST_F(ZfhEncodingTest, FmvXh) {
+  enc_->ParseInstruction(kFmvXh);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32ZfhMin, 0), OpcodeEnum::kFmvXh);
+}
+
+TEST_F(ZfhEncodingTest, FmvXh_frs1) {
+  FloatFrs1Helper(kFmvXh, OpcodeEnum::kFmvXh);
+}
+
+TEST_F(ZfhEncodingTest, FmvXh_rd) {
+  for (uint32_t rd_index = 1; rd_index < 32; ++rd_index) {
+    uint32_t rd_adjustment = rd_index << 7;
+    uint32_t expected_value = absl::Uniform<uint32_t>(gen_);
+    RVXRegister *rd_reg;
+    std::tie(rd_reg, std::ignore) =
+        state_->GetRegister<RVXRegister>(absl::StrCat("x", rd_index));
+    rd_reg->data_buffer()->Set<uint32_t>(0, expected_value);
+    enc_->ParseInstruction(kFmvXh | rd_adjustment);
+    std::unique_ptr<RegisterDestinationOperand<RVXRegister>> dst(
+        static_cast<RegisterDestinationOperand<RVXRegister> *>(
+            enc_->GetDestination(SlotEnum::kRiscv32ZfhMin, 0,
+                                 OpcodeEnum::kFmvXh, DestOpEnum::kRd, 0, 0)));
+    uint32_t observed_value =
+        dst->GetRegister()->data_buffer()->Get<uint32_t>(0);
+    EXPECT_EQ(observed_value, expected_value);
+  }
+}
+
+TEST_F(ZfhEncodingTest, FmvHx) {
+  enc_->ParseInstruction(kFmvHx);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32ZfhMin, 0), OpcodeEnum::kFmvHx);
+}
+
+TEST_F(ZfhEncodingTest, FmvHx_rs1) {
+  ScalarRs1Helper(kFmvHx, OpcodeEnum::kFmvHx);
+}
+
+TEST_F(ZfhEncodingTest, FmvHx_frd) {
+  FloatFrdHelper(kFmvHx, OpcodeEnum::kFmvHx);
+}
+
+TEST_F(ZfhEncodingTest, FcvtSh) {
+  enc_->ParseInstruction(kFcvtSh);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32ZfhMin, 0), OpcodeEnum::kFcvtSh);
+}
+
+TEST_F(ZfhEncodingTest, FcvtSh_frs1) {
+  FloatFrs1Helper(kFcvtSh, OpcodeEnum::kFcvtSh);
+}
+
+TEST_F(ZfhEncodingTest, FcvtSh_frd) {
+  FloatFrdHelper(kFcvtSh, OpcodeEnum::kFcvtSh);
+}
+
+TEST_F(ZfhEncodingTest, FcvtSh_rm) {
+  FloatRmHelper(kFcvtSh, OpcodeEnum::kFcvtSh);
+}
+
+TEST_F(ZfhEncodingTest, FcvtHs) {
+  enc_->ParseInstruction(kFcvtHs);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32ZfhMin, 0), OpcodeEnum::kFcvtHs);
+}
+
+TEST_F(ZfhEncodingTest, FcvtHs_frs1) {
+  FloatFrs1Helper(kFcvtHs, OpcodeEnum::kFcvtHs);
+}
+
+TEST_F(ZfhEncodingTest, FcvtHs_frd) {
+  FloatFrdHelper(kFcvtHs, OpcodeEnum::kFcvtHs);
+}
+
+TEST_F(ZfhEncodingTest, FcvtHs_rm) {
+  FloatRmHelper(kFcvtHs, OpcodeEnum::kFcvtHs);
+}
+
+TEST_F(ZfhEncodingTest, FcvtDh) {
+  enc_->ParseInstruction(kFcvtDh);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32ZfhMin, 0), OpcodeEnum::kFcvtDh);
+}
+
+TEST_F(ZfhEncodingTest, FcvtDh_frs1) {
+  FloatFrs1Helper(kFcvtDh, OpcodeEnum::kFcvtDh);
+}
+
+TEST_F(ZfhEncodingTest, FcvtDh_frd) {
+  FloatFrdHelper(kFcvtDh, OpcodeEnum::kFcvtDh);
+}
+
+TEST_F(ZfhEncodingTest, FcvtDh_rm) {
+  FloatRmHelper(kFcvtDh, OpcodeEnum::kFcvtDh);
+}
+
+TEST_F(ZfhEncodingTest, FcvtHd) {
+  enc_->ParseInstruction(kFcvtHd);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscv32ZfhMin, 0), OpcodeEnum::kFcvtHd);
+}
+
+TEST_F(ZfhEncodingTest, FcvtHd_frs1) {
+  FloatFrs1Helper(kFcvtHd, OpcodeEnum::kFcvtHd);
+}
+
+TEST_F(ZfhEncodingTest, FcvtHd_frd) {
+  FloatFrdHelper(kFcvtHd, OpcodeEnum::kFcvtHd);
+}
+
+TEST_F(ZfhEncodingTest, FcvtHd_rm) {
+  FloatRmHelper(kFcvtHd, OpcodeEnum::kFcvtHd);
+}
+
+}  // namespace