Add support for vector basic bit manipulation instructions.

PiperOrigin-RevId: 713788510
Change-Id: I83bfe91e781828f1f463117245b4fde1fe2ff272
diff --git a/riscv/BUILD b/riscv/BUILD
index cd96797..d3e2c14 100644
--- a/riscv/BUILD
+++ b/riscv/BUILD
@@ -308,6 +308,30 @@
     ],
 )
 
+# TODO(julianmb): Remove this target once there is a rva23_instructions target.
+cc_library(
+    name = "riscv_vector_basic_bit_manipulation_instructions",
+    srcs = [
+        "riscv_vector_basic_bit_manipulation_instructions.cc",
+    ],
+    hdrs = [
+        "riscv_vector_basic_bit_manipulation_instructions.h",
+        "riscv_vector_instruction_helpers.h",
+    ],
+    copts = [
+        "-O3",
+        "-ffp-model=strict",
+    ],
+    deps = [
+        ":riscv_state",
+        "@com_google_absl//absl/log",
+        "@com_google_absl//absl/numeric:bits",
+        "@com_google_absl//absl/types:span",
+        "@com_google_mpact-sim//mpact/sim/generic:instruction",
+        "@com_google_mpact-sim//mpact/sim/generic:type_helpers",
+    ],
+)
+
 mpact_isa_decoder(
     name = "riscv32g_isa",
     src = "riscv32g.isa",
@@ -606,6 +630,35 @@
     ],
 )
 
+mpact_isa_decoder(
+    name = "zvbb_isa",
+    src = "riscv_zvbb.isa",
+    includes = [
+        "riscv_vector.isa",
+    ],
+    isa_name = "ZVBB",
+    prefix = "zvbb",
+    deps = [
+        ":riscv_v",
+        ":riscv_vector_basic_bit_manipulation_instructions",
+        "@com_google_absl//absl/functional:bind_front",
+    ],
+)
+
+mpact_bin_fmt_decoder(
+    name = "zvbb_bin_fmt",
+    src = "riscv_zvbb.bin_fmt",
+    decoder_name = "ZVBB",
+    includes = [
+        "riscv32g.bin_fmt",
+        "riscv_vector.bin_fmt",
+    ],
+    prefix = "zvbb",
+    deps = [
+        ":zvbb_isa",
+    ],
+)
+
 cc_library(
     name = "riscv32g_decoder",
     srcs = [
@@ -644,6 +697,7 @@
         "riscv_getters_zba.h",
         "riscv_getters_zbb32.h",
         "riscv_getters_zbb64.h",
+        "riscv_getters_zvbb.h",
     ],
     deps = [
         ":riscv_encoding_common",
@@ -872,6 +926,41 @@
 )
 
 cc_library(
+    name = "zvbb_decoder",
+    srcs = [
+        "zvbb_decoder.cc",
+        "zvbb_encoding.cc",
+    ],
+    hdrs = [
+        "zvbb_decoder.h",
+        "zvbb_encoding.h",
+    ],
+    copts = ["-O3"],
+    deps = [
+        ":riscv_encoding_common",
+        ":riscv_getters",
+        ":riscv_state",
+        ":riscv_vector_basic_bit_manipulation_instructions",
+        ":zvbb_bin_fmt",
+        ":zvbb_isa",
+        "@com_google_absl//absl/base",
+        "@com_google_absl//absl/container:flat_hash_map",
+        "@com_google_absl//absl/functional:any_invocable",
+        "@com_google_absl//absl/functional:bind_front",
+        "@com_google_absl//absl/log",
+        "@com_google_absl//absl/strings",
+        "@com_google_absl//absl/strings:str_format",
+        "@com_google_absl//absl/types:span",
+        "@com_google_mpact-sim//mpact/sim/generic:arch_state",
+        "@com_google_mpact-sim//mpact/sim/generic:core",
+        "@com_google_mpact-sim//mpact/sim/generic:instruction",
+        "@com_google_mpact-sim//mpact/sim/generic:program_error",
+        "@com_google_mpact-sim//mpact/sim/generic:type_helpers",
+        "@com_google_mpact-sim//mpact/sim/util/memory",
+    ],
+)
+
+cc_library(
     name = "riscv_top",
     srcs = [
         "riscv_top.cc",
diff --git a/riscv/riscv_getters_zvbb.h b/riscv/riscv_getters_zvbb.h
new file mode 100644
index 0000000..7484890
--- /dev/null
+++ b/riscv/riscv_getters_zvbb.h
@@ -0,0 +1,104 @@
+// 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_RISCV_GETTERS_ZVBB_H_
+#define THIRD_PARTY_MPACT_RISCV_RISCV_GETTERS_ZVBB_H_
+
+#include <cstdint>
+#include <new>
+
+#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).
+
+// The following function adds source operand getters to the given getter map.
+// The function uses the template parameters to get the correct enum type
+// for the instruction set being decoded. The Extractors parameter is used to
+// get the correct instruction format extractor for the instruction set.
+template <typename Enum, typename Extractors, typename VectorRegister>
+void AddRiscVZvbbSourceVectorGetters(SourceOpGetterMap &getter_map,
+                                     RiscVEncodingCommon *common) {
+  // Source operand getters.
+  Insert(getter_map, *Enum::kVmask, [common]() -> SourceOperandInterface * {
+    auto vm = Extractors::VArith::ExtractVm(common->inst_word());
+    if (vm == 1) {
+      // Unmasked, return the True mask.
+      return new RV32VectorTrueOperand(common->state());
+    }
+    // Masked. Return the mask register.
+    return mpact::sim::riscv::GetVectorMaskRegisterSourceOp<VectorRegister>(
+        common->state(), 0);
+  });
+  Insert(getter_map, *Enum::kVs1, [common]() -> SourceOperandInterface * {
+    auto num = Extractors::VArith::ExtractVs1(common->inst_word());
+    return mpact::sim::riscv::GetVectorMaskRegisterSourceOp<VectorRegister>(
+        common->state(), num);
+  });
+  Insert(getter_map, *Enum::kVs2, [common]() -> SourceOperandInterface * {
+    auto num = Extractors::VArith::ExtractVs2(common->inst_word());
+    return mpact::sim::riscv::GetVectorMaskRegisterSourceOp<VectorRegister>(
+        common->state(), num);
+  });
+}
+
+template <typename Enum, typename Extractors, typename IntegerRegister>
+void AddRiscvZvbbSourceScalarGetters(SourceOpGetterMap &getter_map,
+                                     RiscVEncodingCommon *common) {
+  // Source operand getters.
+  Insert(getter_map, *Enum::kRs1, [common]() -> SourceOperandInterface * {
+    int num = Extractors::VArith::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]);
+  });
+  Insert(getter_map, *Enum::kUimm5, [common]() -> SourceOperandInterface * {
+    const auto num = Extractors::VArith::ExtractUimm5(common->inst_word());
+    return new generic::ImmediateOperand<int32_t>(num);
+  });
+  Insert(getter_map, *Enum::kUimm6, [common]() -> SourceOperandInterface * {
+    const auto num = Extractors::VArith::ExtractUimm6(common->inst_word());
+    return new generic::ImmediateOperand<int32_t>(num);
+  });
+}
+
+template <typename Enum, typename Extractors, typename VectorRegister>
+void AddRiscVZvbbDestGetters(DestOpGetterMap &getter_map,
+                             RiscVEncodingCommon *common) {
+  // Destination operand getters.
+  Insert(getter_map, *Enum::kVd,
+         [common](int latency) -> DestinationOperandInterface * {
+           auto num = Extractors::VArith::ExtractVd(common->inst_word());
+           return mpact::sim::riscv::GetVectorRegisterDestinationOp<
+               VectorRegister>(common->state(), latency, num);
+         });
+}
+
+}  // namespace riscv
+}  // namespace sim
+}  // namespace mpact
+
+#endif  // THIRD_PARTY_MPACT_RISCV_RISCV_GETTERS_ZVBB_H_
diff --git a/riscv/riscv_vector.bin_fmt b/riscv/riscv_vector.bin_fmt
index cad9aea..11a1db0 100644
--- a/riscv/riscv_vector.bin_fmt
+++ b/riscv/riscv_vector.bin_fmt
@@ -43,6 +43,8 @@
     unsigned opcode[7];
   overlays:
     unsigned uimm5[5] = vs1;
+    unsigned uimm6[6] = func6[0], vs1;
+    unsigned func5[5] = func6[5..1];
     signed simm5[5] = vs1;
     unsigned rd[5] = vd;
     unsigned rs1[5] = vs1;
diff --git a/riscv/riscv_vector_basic_bit_manipulation_instructions.cc b/riscv/riscv_vector_basic_bit_manipulation_instructions.cc
new file mode 100644
index 0000000..d6d8273
--- /dev/null
+++ b/riscv/riscv_vector_basic_bit_manipulation_instructions.cc
@@ -0,0 +1,370 @@
+// 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/riscv_vector_basic_bit_manipulation_instructions.h"
+
+#include <cstdint>
+
+#include "absl/log/log.h"
+#include "absl/numeric/bits.h"
+#include "absl/types/span.h"
+#include "mpact/sim/generic/type_helpers.h"
+#include "riscv/riscv_register.h"
+#include "riscv/riscv_state.h"
+#include "riscv/riscv_vector_instruction_helpers.h"
+#include "riscv/riscv_vector_state.h"
+
+using ::mpact::sim::generic::operator*;  // NOLINT: is used below (clang error).
+
+namespace mpact {
+namespace sim {
+namespace riscv {
+
+void RV32VUnimplementedInstruction(const Instruction *inst) {
+  auto *state = static_cast<RiscVState *>(inst->state());
+  state->Trap(/*is_interrupt*/ false, /*trap_value*/ 0,
+              *ExceptionCode::kIllegalInstruction,
+              /*epc*/ inst->address(), inst);
+}
+
+namespace {
+template <typename T>
+T BitReverse(T input) {
+  T result = 0;
+  for (int i = 0; i < sizeof(T) * 8; ++i) {
+    result <<= 1;
+    result |= (input & 1);
+    input >>= 1;
+  }
+  return result;
+}
+
+template <class T>
+constexpr T ByteSwap(T input) {
+  // TODO(julianmb): Once c++23 is supported, use std::byteswap.
+  T result = 0;
+  for (int i = 0; i < sizeof(T); ++i) {
+    result |= ((input >> (i * 8)) & 0xFF) << ((sizeof(T) - 1 - i) * 8);
+  }
+  return result;
+}
+}  // namespace
+
+void Vandn(Instruction *inst) {
+  auto *rv_vector = static_cast<RiscVState *>(inst->state())->rv_vector();
+  int sew = rv_vector->selected_element_width();
+  switch (sew) {
+    case 1:
+      return RiscVBinaryVectorOp<uint8_t, uint8_t, uint8_t>(
+          rv_vector, inst,
+          [](uint8_t vs2, uint8_t vs1) -> uint8_t { return vs2 & ~vs1; });
+    case 2:
+      return RiscVBinaryVectorOp<uint16_t, uint16_t, uint16_t>(
+          rv_vector, inst,
+          [](uint16_t vs2, uint16_t vs1) -> uint16_t { return vs2 & ~vs1; });
+    case 4:
+      return RiscVBinaryVectorOp<uint32_t, uint32_t, uint32_t>(
+          rv_vector, inst,
+          [](uint32_t vs2, uint32_t vs1) -> uint32_t { return vs2 & ~vs1; });
+    case 8:
+      return RiscVBinaryVectorOp<uint64_t, uint64_t, uint64_t>(
+          rv_vector, inst,
+          [](uint64_t vs2, uint64_t vs1) -> uint64_t { return vs2 & ~vs1; });
+    default:
+      rv_vector->set_vector_exception();
+      LOG(ERROR) << "Illegal SEW value";
+      return;
+  }
+}
+
+void Vbrev8(Instruction *inst) {
+  auto *rv_vector = static_cast<RiscVState *>(inst->state())->rv_vector();
+  int sew = rv_vector->selected_element_width();
+  switch (sew) {
+    case 1:
+      return RiscVUnaryVectorOp<uint8_t, uint8_t>(
+          rv_vector, inst,
+          [](uint8_t vs2) -> uint8_t { return BitReverse(vs2); });
+    case 2:
+      return RiscVUnaryVectorOp<uint16_t, uint16_t>(
+          rv_vector, inst, [](uint16_t vs2) -> uint16_t {
+            absl::Span<uint8_t> span =
+                absl::MakeSpan(reinterpret_cast<uint8_t *>(&vs2), sizeof(vs2));
+            for (uint8_t &byte : span) {
+              byte = BitReverse(byte);
+            }
+            return vs2;
+          });
+    case 4:
+      return RiscVUnaryVectorOp<uint32_t, uint32_t>(
+          rv_vector, inst, [](uint32_t vs2) -> uint32_t {
+            absl::Span<uint8_t> span =
+                absl::MakeSpan(reinterpret_cast<uint8_t *>(&vs2), sizeof(vs2));
+            for (uint8_t &byte : span) {
+              byte = BitReverse(byte);
+            }
+            return vs2;
+          });
+    case 8:
+      return RiscVUnaryVectorOp<uint64_t, uint64_t>(
+          rv_vector, inst, [](uint64_t vs2) -> uint64_t {
+            absl::Span<uint8_t> span =
+                absl::MakeSpan(reinterpret_cast<uint8_t *>(&vs2), sizeof(vs2));
+            for (uint8_t &byte : span) {
+              byte = BitReverse(byte);
+            }
+            return vs2;
+          });
+    default:
+      rv_vector->set_vector_exception();
+      LOG(ERROR) << "Illegal SEW value";
+      return;
+  }
+}
+
+void Vrev8(Instruction *inst) {
+  auto *rv_vector = static_cast<RiscVState *>(inst->state())->rv_vector();
+  int sew = rv_vector->selected_element_width();
+  switch (sew) {
+    case 1:
+      return RiscVUnaryVectorOp<uint8_t, uint8_t>(
+          rv_vector, inst, [](uint8_t vs2) -> uint8_t { return vs2; });
+    case 2:
+      return RiscVUnaryVectorOp<uint16_t, uint16_t>(
+          rv_vector, inst,
+          [](uint16_t vs2) -> uint16_t { return ByteSwap(vs2); });
+    case 4:
+      return RiscVUnaryVectorOp<uint32_t, uint32_t>(
+          rv_vector, inst,
+          [](uint32_t vs2) -> uint32_t { return ByteSwap(vs2); });
+    case 8:
+      return RiscVUnaryVectorOp<uint64_t, uint64_t>(
+          rv_vector, inst,
+          [](uint64_t vs2) -> uint64_t { return ByteSwap(vs2); });
+    default:
+      rv_vector->set_vector_exception();
+      LOG(ERROR) << "Illegal SEW value";
+      return;
+  }
+}
+
+void Vrol(Instruction *inst) {
+  auto *rv_vector = static_cast<RiscVState *>(inst->state())->rv_vector();
+  int sew = rv_vector->selected_element_width();
+  switch (sew) {
+    case 1:
+      return RiscVBinaryVectorOp<uint8_t, uint8_t, uint8_t>(
+          rv_vector, inst, [](uint8_t vs2, uint8_t vs1) -> uint8_t {
+            uint8_t rotate_amount = vs1 & 0b0000'0111;
+            return absl::rotl(vs2, rotate_amount);
+          });
+    case 2:
+      return RiscVBinaryVectorOp<uint16_t, uint16_t, uint16_t>(
+          rv_vector, inst, [](uint16_t vs2, uint16_t vs1) -> uint16_t {
+            uint8_t rotate_amount = vs1 & 0b0000'1111;
+            return absl::rotl(vs2, rotate_amount);
+          });
+    case 4:
+      return RiscVBinaryVectorOp<uint32_t, uint32_t, uint32_t>(
+          rv_vector, inst, [](uint32_t vs2, uint32_t vs1) -> uint32_t {
+            uint8_t rotate_amount = vs1 & 0b0001'1111;
+            return absl::rotl(vs2, rotate_amount);
+          });
+    case 8:
+      return RiscVBinaryVectorOp<uint64_t, uint64_t, uint64_t>(
+          rv_vector, inst, [](uint64_t vs2, uint64_t vs1) -> uint64_t {
+            uint8_t rotate_amount = vs1 & 0b0011'1111;
+            return absl::rotl(vs2, rotate_amount);
+          });
+    default:
+      rv_vector->set_vector_exception();
+      LOG(ERROR) << "Illegal SEW value";
+      return;
+  }
+}
+
+void Vror(Instruction *inst) {
+  auto *rv_vector = static_cast<RiscVState *>(inst->state())->rv_vector();
+  int sew = rv_vector->selected_element_width();
+  switch (sew) {
+    case 1:
+      return RiscVBinaryVectorOp<uint8_t, uint8_t, uint8_t>(
+          rv_vector, inst, [](uint8_t vs2, uint8_t vs1) -> uint8_t {
+            uint8_t rotate_amount = vs1 & 0b0000'0111;
+            return absl::rotr(vs2, rotate_amount);
+          });
+    case 2:
+      return RiscVBinaryVectorOp<uint16_t, uint16_t, uint16_t>(
+          rv_vector, inst, [](uint16_t vs2, uint16_t vs1) -> uint16_t {
+            uint8_t rotate_amount = vs1 & 0b0000'1111;
+            return absl::rotr(vs2, rotate_amount);
+          });
+    case 4:
+      return RiscVBinaryVectorOp<uint32_t, uint32_t, uint32_t>(
+          rv_vector, inst, [](uint32_t vs2, uint32_t vs1) -> uint32_t {
+            uint8_t rotate_amount = vs1 & 0b0001'1111;
+            return absl::rotr(vs2, rotate_amount);
+          });
+    case 8:
+      return RiscVBinaryVectorOp<uint64_t, uint64_t, uint64_t>(
+          rv_vector, inst, [](uint64_t vs2, uint64_t vs1) -> uint64_t {
+            uint8_t rotate_amount = vs1 & 0b0011'1111;
+            return absl::rotr(vs2, rotate_amount);
+          });
+    default:
+      rv_vector->set_vector_exception();
+      LOG(ERROR) << "Illegal SEW value";
+      return;
+  }
+}
+
+// Instructions that are only in Zvbb
+
+void Vbrev(Instruction *inst) {
+  auto *rv_vector = static_cast<RiscVState *>(inst->state())->rv_vector();
+  int sew = rv_vector->selected_element_width();
+  switch (sew) {
+    case 1:
+      return RiscVUnaryVectorOp<uint8_t, uint8_t>(
+          rv_vector, inst,
+          [](uint8_t vs2) -> uint8_t { return BitReverse(vs2); });
+    case 2:
+      return RiscVUnaryVectorOp<uint16_t, uint16_t>(
+          rv_vector, inst,
+          [](uint16_t vs2) -> uint16_t { return BitReverse(vs2); });
+    case 4:
+      return RiscVUnaryVectorOp<uint32_t, uint32_t>(
+          rv_vector, inst,
+          [](uint32_t vs2) -> uint32_t { return BitReverse(vs2); });
+    case 8:
+      return RiscVUnaryVectorOp<uint64_t, uint64_t>(
+          rv_vector, inst,
+          [](uint64_t vs2) -> uint64_t { return BitReverse(vs2); });
+    default:
+      rv_vector->set_vector_exception();
+      LOG(ERROR) << "Illegal SEW value";
+      return;
+  }
+}
+
+void Vclz(Instruction *inst) {
+  auto *rv_vector = static_cast<RiscVState *>(inst->state())->rv_vector();
+  int sew = rv_vector->selected_element_width();
+  switch (sew) {
+    case 1:
+      return RiscVUnaryVectorOp<uint8_t, uint8_t>(
+          rv_vector, inst,
+          [](uint8_t vs2) -> uint8_t { return absl::countl_zero(vs2); });
+    case 2:
+      return RiscVUnaryVectorOp<uint16_t, uint16_t>(
+          rv_vector, inst,
+          [](uint16_t vs2) -> uint16_t { return absl::countl_zero(vs2); });
+    case 4:
+      return RiscVUnaryVectorOp<uint32_t, uint32_t>(
+          rv_vector, inst,
+          [](uint32_t vs2) -> uint32_t { return absl::countl_zero(vs2); });
+    case 8:
+      return RiscVUnaryVectorOp<uint64_t, uint64_t>(
+          rv_vector, inst,
+          [](uint64_t vs2) -> uint64_t { return absl::countl_zero(vs2); });
+    default:
+      rv_vector->set_vector_exception();
+      LOG(ERROR) << "Illegal SEW value";
+      return;
+  }
+}
+
+void Vctz(Instruction *inst) {
+  auto *rv_vector = static_cast<RiscVState *>(inst->state())->rv_vector();
+  int sew = rv_vector->selected_element_width();
+  switch (sew) {
+    case 1:
+      return RiscVUnaryVectorOp<uint8_t, uint8_t>(
+          rv_vector, inst,
+          [](uint8_t vs2) -> uint8_t { return absl::countr_zero(vs2); });
+    case 2:
+      return RiscVUnaryVectorOp<uint16_t, uint16_t>(
+          rv_vector, inst,
+          [](uint16_t vs2) -> uint16_t { return absl::countr_zero(vs2); });
+    case 4:
+      return RiscVUnaryVectorOp<uint32_t, uint32_t>(
+          rv_vector, inst,
+          [](uint32_t vs2) -> uint32_t { return absl::countr_zero(vs2); });
+    case 8:
+      return RiscVUnaryVectorOp<uint64_t, uint64_t>(
+          rv_vector, inst,
+          [](uint64_t vs2) -> uint64_t { return absl::countr_zero(vs2); });
+    default:
+      rv_vector->set_vector_exception();
+      LOG(ERROR) << "Illegal SEW value";
+      return;
+  }
+}
+
+void VectorVcpop(Instruction *inst) {
+  auto *rv_vector = static_cast<RiscVState *>(inst->state())->rv_vector();
+  int sew = rv_vector->selected_element_width();
+  switch (sew) {
+    case 1:
+      return RiscVUnaryVectorOp<uint8_t, uint8_t>(
+          rv_vector, inst,
+          [](uint8_t vs2) -> uint8_t { return absl::popcount(vs2); });
+    case 2:
+      return RiscVUnaryVectorOp<uint16_t, uint16_t>(
+          rv_vector, inst,
+          [](uint16_t vs2) -> uint16_t { return absl::popcount(vs2); });
+    case 4:
+      return RiscVUnaryVectorOp<uint32_t, uint32_t>(
+          rv_vector, inst,
+          [](uint32_t vs2) -> uint32_t { return absl::popcount(vs2); });
+    case 8:
+      return RiscVUnaryVectorOp<uint64_t, uint64_t>(
+          rv_vector, inst,
+          [](uint64_t vs2) -> uint64_t { return absl::popcount(vs2); });
+    default:
+      rv_vector->set_vector_exception();
+      LOG(ERROR) << "Illegal SEW value";
+      return;
+  }
+}
+
+void Vwsll(Instruction *inst) {
+  auto *rv_vector = static_cast<RiscVState *>(inst->state())->rv_vector();
+  int sew = rv_vector->selected_element_width();
+  switch (sew) {
+    case 1:
+      return RiscVBinaryVectorOp<uint16_t, uint8_t, uint8_t>(
+          rv_vector, inst, [](uint8_t vs2, uint8_t vs1) -> uint16_t {
+            return static_cast<uint16_t>(vs2) << (vs1 & 0x0F);
+          });
+    case 2:
+      return RiscVBinaryVectorOp<uint32_t, uint16_t, uint16_t>(
+          rv_vector, inst, [](uint16_t vs2, uint16_t vs1) -> uint32_t {
+            return static_cast<uint32_t>(vs2) << (vs1 & 0x1F);
+          });
+    case 4:
+      return RiscVBinaryVectorOp<uint64_t, uint32_t, uint32_t>(
+          rv_vector, inst, [](uint32_t vs2, uint32_t vs1) -> uint64_t {
+            return static_cast<uint64_t>(vs2) << (vs1 & 0x3F);
+          });
+    default:
+      rv_vector->set_vector_exception();
+      LOG(ERROR) << "Illegal SEW value";
+      return;
+  }
+}
+
+}  // namespace riscv
+}  // namespace sim
+}  // namespace mpact
diff --git a/riscv/riscv_vector_basic_bit_manipulation_instructions.h b/riscv/riscv_vector_basic_bit_manipulation_instructions.h
new file mode 100644
index 0000000..9c4f1c6
--- /dev/null
+++ b/riscv/riscv_vector_basic_bit_manipulation_instructions.h
@@ -0,0 +1,51 @@
+// 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_RISCV_BASIC_BIT_MANIPULATION_INSTRUCTIONS_H_
+#define THIRD_PARTY_MPACT_RISCV_RISCV_BASIC_BIT_MANIPULATION_INSTRUCTIONS_H_
+
+#include "mpact/sim/generic/instruction.h"
+
+namespace mpact {
+namespace sim {
+namespace riscv {
+
+using Instruction = ::mpact::sim::generic::Instruction;
+
+void RV32VUnimplementedInstruction(const Instruction *inst);
+
+// Vector bit manipulation instructions.
+
+// Zvkb subset of instructions
+void Vandn(Instruction *);
+void Vbrev8(Instruction *);
+void Vrev8(Instruction *);
+void Vrol(Instruction *);
+void Vror(Instruction *);
+
+// Zvbb instructions
+void Vbrev(Instruction *);
+void Vclz(Instruction *);
+void Vctz(Instruction *);
+// There is a name collision with an existing Vcpop instruction that stores the
+// result in a scalar register. This implementation stores the result in a
+// vector register.
+void VectorVcpop(Instruction *);
+void Vwsll(Instruction *);
+
+}  // namespace riscv
+}  // namespace sim
+}  // namespace mpact
+
+#endif  // THIRD_PARTY_MPACT_RISCV_RISCV_BASIC_BIT_MANIPULATION_INSTRUCTIONS_H_
diff --git a/riscv/riscv_zvbb.bin_fmt b/riscv/riscv_zvbb.bin_fmt
new file mode 100644
index 0000000..a9fb0f7
--- /dev/null
+++ b/riscv/riscv_zvbb.bin_fmt
@@ -0,0 +1,47 @@
+// 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"
+#include "riscv/riscv_vector.bin_fmt"
+
+decoder ZVBB {
+  namespace mpact::sim::riscv::zvbb;
+  opcode_enum = "OpcodeEnum";
+  includes {
+    #include "riscv/zvbb_decoder.h"
+  }
+  RiscVZvbbInst32 = { RiscVZvkbInst32, RiscVBasicBitInst32 };
+}
+
+instruction group RiscVZvkbInst32[32] : VArith {
+  vandn_vv : VArith : func6 == 0b000'001, func3 == 0b000, opcode == 0b101'0111;
+  vandn_vx : VArith : func6 == 0b000'001, func3 == 0b100, opcode == 0b101'0111;
+  vbrev8_v : VArith : func6 == 0b010'010, func3 == 0b010, opcode == 0b101'0111, vs1 == 0b01'000;
+  vrev8_v  : VArith : func6 == 0b010'010, func3 == 0b010, opcode == 0b101'0111, vs1 == 0b01'001;
+  vrol_vv  : VArith : func6 == 0b010'101, func3 == 0b000, opcode == 0b101'0111;
+  vrol_vx  : VArith : func6 == 0b010'101, func3 == 0b100, opcode == 0b101'0111;
+  vror_vv  : VArith : func6 == 0b010'100, func3 == 0b000, opcode == 0b101'0111;
+  vror_vx  : VArith : func6 == 0b010'100, func3 == 0b100, opcode == 0b101'0111;
+  vror_vi  : VArith : func5 == 0b01010, func3 == 0b011, opcode == 0b101'0111;
+}
+
+instruction group RiscVBasicBitInst32[32] : VArith {
+  vbrev_v : VArith : func6 == 0b010'010, func3 == 0b010, opcode == 0b101'0111, vs1 == 0b01'010;
+  vclz_v  : VArith : func6 == 0b010'010, func3 == 0b010, opcode == 0b101'0111, vs1 == 0b01'100;
+  vctz_v  : VArith : func6 == 0b010'010, func3 == 0b010, opcode == 0b101'0111, vs1 == 0b01'101;
+  vcpop_v : VArith : func6 == 0b010'010, func3 == 0b010, opcode == 0b101'0111, vs1 == 0b01'110;
+  vwsll_vv : VArith : func6 == 0b110'101, func3 == 0b000, opcode == 0b101'0111;
+  vwsll_vx : VArith : func6 == 0b110'101, func3 == 0b100, opcode == 0b101'0111;
+  vwsll_vi : VArith : func6 == 0b110'101, func3 == 0b011, opcode == 0b101'0111;
+}
diff --git a/riscv/riscv_zvbb.isa b/riscv/riscv_zvbb.isa
new file mode 100644
index 0000000..f6f5926
--- /dev/null
+++ b/riscv/riscv_zvbb.isa
@@ -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
+//
+//     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.
+
+// This file contains the ISA description of the RiscV zvbb extention
+// instructions.
+
+isa ZVBB {
+  namespace mpact::sim::riscv::zvbb;
+  slots {
+    riscv_zvbb;
+  }
+}
+
+// First disasm field is 18 char wide and left justified.
+disasm widths = {-18};
+
+#include "riscv/riscv_vector.isa"
+
+slot riscv_zvkb {
+  includes {
+    #include "riscv/riscv_vector_basic_bit_manipulation_instructions.h"
+  }
+  default size = 4;
+  default latency = 0;
+  default opcode =
+    disasm: "Unimplemented instruction at 0x%(@:08x)",
+    semfunc: "&RV32VUnimplementedInstruction";
+  opcodes {
+    vandn_vv{: vs2, vs1, vmask : vd},
+      disasm: "vandn.vv", "%vd, %vs2, %vs1, %vmask",
+      semfunc: "&Vandn";
+    vandn_vx{: vs2, rs1, vmask : vd},
+      disasm: "vandn.vx", "%vd, %vs2, %rs1, %vmask",
+      semfunc: "&Vandn";
+    vbrev8_v{: vs2, vmask : vd},
+      disasm: "vbrev8.v", "%vd, %vs2, %vmask",
+      semfunc: "&Vbrev8";
+    vrev8_v{: vs2, vmask : vd},
+      disasm: "vrev8.v", "%vd, %vs2, %vmask",
+      semfunc: "&Vrev8";
+    vrol_vv{: vs2, vs1, vmask : vd},
+      disasm: "vrol.vv", "%vd, %vs2, %vs1, %vmask",
+      semfunc: "&Vrol";
+    vrol_vx{: vs2, rs1, vmask : vd},
+      disasm: "vrol.vx", "%vd, %vs2, %rs1, %vmask",
+      semfunc: "&Vrol";
+    vror_vv{: vs2, vs1, vmask : vd},
+      disasm: "vror.vv", "%vd, %vs2, %vs1, %vmask",
+      semfunc: "&Vror";
+    vror_vx{: vs2, rs1, vmask : vd},
+      disasm: "vror.vx", "%vd, %vs2, %rs1, %vmask",
+      semfunc: "&Vror";
+    vror_vi{: vs2, uimm6, vmask : vd},
+      disasm: "vror.vi", "%vd, %vs2, %uimm6, %vmask",
+      semfunc: "&Vror";
+  }
+}
+
+slot riscv_zvbb : riscv_zvkb {
+  includes {
+    #include "riscv/riscv_vector_basic_bit_manipulation_instructions.h"
+  }
+  default size = 4;
+  default latency = 0;
+  default opcode =
+    disasm: "Unimplemented instruction at 0x%(@:08x)",
+    semfunc: "&RV32VUnimplementedInstruction";
+  opcodes {
+    vbrev_v{: vs2, vmask : vd},
+      disasm: "vbrev.v", "%vd, %vs2, %vmask",
+      semfunc: "&Vbrev";
+    vclz_v{: vs2, vmask : vd},
+      disasm: "vclz.v", "%vd, %vs2, %vmask",
+      semfunc: "&Vclz";
+    vctz_v{: vs2, vmask : vd},
+      disasm: "vctz.v", "%vd, %vs2, %vmask",
+      semfunc: "&Vctz";
+    vcpop_v{: vs2, vmask : vd},
+      disasm: "vcpop.v", "%vd, %vs2, %vmask",
+      semfunc: "&VectorVcpop";
+    vwsll_vv{: vs2, vs1, vmask : vd},
+      disasm: "vwsll.vv", "%vd, %vs2, %vs1, %vmask",
+      semfunc: "&Vwsll";
+    vwsll_vx{: vs2, rs1, vmask : vd},
+      disasm: "vwsll.vx", "%vd, %vs2, %rs1, %vmask",
+      semfunc: "&Vwsll";
+    vwsll_vi{: vs2, uimm5, vmask : vd},
+      disasm: "vwsll.vi", "%vd, %vs2, %uimm5, %vmask",
+      semfunc: "&Vwsll";
+  }
+}
+
diff --git a/riscv/test/BUILD b/riscv/test/BUILD
index ea7a94d..4ea63fe 100644
--- a/riscv/test/BUILD
+++ b/riscv/test/BUILD
@@ -334,6 +334,23 @@
 )
 
 cc_test(
+    name = "zvbb_encoding_test",
+    size = "small",
+    srcs = [
+        "zvbb_encoding_test.cc",
+    ],
+    deps = [
+        "//riscv:riscv32g_bitmanip_decoder",
+        "//riscv:riscv_state",
+        "//riscv:zvbb_decoder",
+        "//riscv:zvbb_isa",
+        "@com_google_googletest//:gtest_main",
+        "@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",
     srcs = [
@@ -891,6 +908,24 @@
     ],
 )
 
+cc_test(
+    name = "riscv_vector_basic_bit_manipulation_instructions_test",
+    size = "small",
+    srcs = [
+        "riscv_vector_basic_bit_manipulation_test.cc",
+    ],
+    deps = [
+        ":riscv_vector_instructions_test_base",
+        "//riscv:riscv_state",
+        "//riscv:riscv_v",
+        "//riscv:riscv_vector_basic_bit_manipulation_instructions",
+        "@com_google_absl//absl/strings",
+        "@com_google_googletest//:gtest_main",
+        "@com_google_mpact-sim//mpact/sim/generic:instruction",
+        "@com_google_mpact-sim//mpact/sim/generic:type_helpers",
+    ],
+)
+
 config_setting(
     name = "arm_cpu",
     values = {"cpu": "arm"},
diff --git a/riscv/test/riscv_vector_basic_bit_manipulation_test.cc b/riscv/test/riscv_vector_basic_bit_manipulation_test.cc
new file mode 100644
index 0000000..923f3cd
--- /dev/null
+++ b/riscv/test/riscv_vector_basic_bit_manipulation_test.cc
@@ -0,0 +1,458 @@
+// 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 <sys/types.h>
+
+#include <cstdint>
+
+#include "absl/strings/str_cat.h"
+#include "googlemock/include/gmock/gmock.h"
+#include "mpact/sim/generic/instruction.h"
+#include "mpact/sim/generic/type_helpers.h"
+#include "riscv/riscv_register.h"
+#include "riscv/riscv_vector_basic_bit_manipulation_instructions.h"
+#include "riscv/test/riscv_vector_instructions_test_base.h"
+
+// This file contains tests for the RiscV vector basic bit manipulations.
+
+namespace {
+
+using ::mpact::sim::generic::WideType;
+using ::mpact::sim::riscv::RV32Register;
+using ::mpact::sim::riscv::RVVectorRegister;
+using ::mpact::sim::riscv::Vandn;
+using ::mpact::sim::riscv::Vbrev;
+using ::mpact::sim::riscv::Vbrev8;
+using ::mpact::sim::riscv::Vclz;
+using ::mpact::sim::riscv::Vctz;
+using ::mpact::sim::riscv::VectorVcpop;
+using ::mpact::sim::riscv::Vrev8;
+using ::mpact::sim::riscv::Vrol;
+using ::mpact::sim::riscv::Vror;
+using ::mpact::sim::riscv::Vwsll;
+using ::mpact::sim::riscv::test::RiscVVectorInstructionsTestBase;
+
+class RiscVVectorBasicBitManipulationTest
+    : public RiscVVectorInstructionsTestBase {};
+
+// Helper function for testing the vandn_vv instruction. Generate the expected
+// result using the bitwise operator.
+template <typename T>
+inline void VandnVVHelper(RiscVVectorBasicBitManipulationTest *tester) {
+  tester->SetSemanticFunction(&Vandn);
+  tester->BinaryOpTestHelperVV<T, T, T>(
+      absl::StrCat("Vandn", sizeof(T) * 8, "vv"), /*sew*/ sizeof(T) * 8,
+      tester->instruction(), [](T vs2, T vs1) -> T { return ~vs1 & vs2; });
+}
+
+// Helper function for testing the vandn_vx instruction. Generate the expected
+// result using the bitwise operator.
+template <typename T>
+inline void VandnVXHelper(RiscVVectorBasicBitManipulationTest *tester) {
+  tester->SetSemanticFunction(&Vandn);
+  tester->BinaryOpTestHelperVX<T, T, T>(
+      absl::StrCat("Vandn", sizeof(T) * 8, "vx"), /*sew*/ sizeof(T) * 8,
+      tester->instruction(), [](T vs2, T rs1) -> T { return ~rs1 & vs2; });
+}
+
+// Helper function for testing the vbrev_v instruction. Generate the expected
+// result by reversing the input bits.
+template <typename T>
+inline void VbrevVHelper(RiscVVectorBasicBitManipulationTest *tester) {
+  tester->SetSemanticFunction(&Vbrev);
+  tester->UnaryOpTestHelperV<T, T>(absl::StrCat("Vbrev", sizeof(T) * 8, "v"),
+                                   /*sew*/ sizeof(T) * 8, tester->instruction(),
+                                   [](T vs2) -> T {
+                                     T result = 0;
+                                     for (int i = 0; i < sizeof(T) * 8; ++i) {
+                                       result = (result << 1) | (vs2 & 1);
+                                       vs2 >>= 1;
+                                     }
+                                     return result;
+                                   });
+}
+
+// Helper function for testing the vbrev8_v instruction. Generate the expected
+// result by reversing the bits in each of the input bytes.
+template <typename T>
+inline void Vbrev8VHelper(RiscVVectorBasicBitManipulationTest *tester) {
+  tester->SetSemanticFunction(&Vbrev8);
+  tester->UnaryOpTestHelperV<T, T>(
+      absl::StrCat("Vbrev8", sizeof(T) * 8, "v"), /*sew*/ sizeof(T) * 8,
+      tester->instruction(), [](T vs2) -> T {
+        T result = 0;
+        for (int offset = 0; offset < sizeof(T) * 8; offset += 8) {
+          uint8_t byte = (vs2 >> offset) & 0xFF;
+          T reversed_byte = 0;
+          for (int j = 0; j < 8; ++j) {
+            reversed_byte = (reversed_byte << 1) | (byte & 1);
+            byte >>= 1;
+          }
+          result |= reversed_byte << offset;
+        }
+        return result;
+      });
+}
+
+// Helper function for testing the vbrev_v instruction. Generate the expected
+// result by reversing the bytes of the input.
+template <typename T>
+inline void Vrev8VHelper(RiscVVectorBasicBitManipulationTest *tester) {
+  tester->SetSemanticFunction(&Vrev8);
+  tester->UnaryOpTestHelperV<T, T>(
+      absl::StrCat("Vrev8", sizeof(T) * 8, "v"), /*sew*/ sizeof(T) * 8,
+      tester->instruction(), [](T vs2) -> T {
+        T result = 0;
+        for (int offset = 0; offset < sizeof(T) * 8; offset += 8) {
+          uint8_t byte = (vs2 >> offset) & 0xff;
+          result = (result << 8) | byte;
+        }
+        return result;
+      });
+}
+
+// Helper function for testing the vrol_vv instruction. Generate the expected
+// result by rotating the input bits left.
+template <typename T>
+inline void VrolVVHelper(RiscVVectorBasicBitManipulationTest *tester) {
+  tester->SetSemanticFunction(&Vrol);
+  tester->BinaryOpTestHelperVV<T, T, T>(
+      absl::StrCat("Vrol", sizeof(T) * 8, "vv"), /*sew*/ sizeof(T) * 8,
+      tester->instruction(), [](T vs2, T vs1) -> T {
+        T rotate_mask = sizeof(T) * 8 - 1;
+        T rotate_amount = (vs1 & rotate_mask);
+        return (vs2 << rotate_amount) |
+               (vs2 >> (sizeof(T) * 8 - rotate_amount));
+      });
+}
+
+// Helper function for testing the vrol_vx instruction. Generate the expected
+// result by rotating the input bits left.
+template <typename T>
+inline void VrolVXHelper(RiscVVectorBasicBitManipulationTest *tester) {
+  tester->SetSemanticFunction(&Vrol);
+  tester->BinaryOpTestHelperVX<T, T, T>(
+      absl::StrCat("Vrol", sizeof(T) * 8, "vx"), /*sew*/ sizeof(T) * 8,
+      tester->instruction(), [](T vs2, T rs1) -> T {
+        T rotate_mask = sizeof(T) * 8 - 1;
+        T rotate_amount = (rs1 & rotate_mask);
+        return (vs2 << rotate_amount) |
+               (vs2 >> (sizeof(T) * 8 - rotate_amount));
+      });
+}
+
+// Helper function for testing the vror_vv instruction. Generate the expected
+// result by rotating the input bits right.
+template <typename T>
+inline void VrorVVHelper(RiscVVectorBasicBitManipulationTest *tester) {
+  tester->SetSemanticFunction(&Vror);
+  tester->BinaryOpTestHelperVV<T, T, T>(
+      absl::StrCat("Vror", sizeof(T) * 8, "vv"), /*sew*/ sizeof(T) * 8,
+      tester->instruction(), [](T vs2, T vs1) -> T {
+        T rotate_mask = sizeof(T) * 8 - 1;
+        T rotate_amount = (vs1 & rotate_mask);
+        return (vs2 >> rotate_amount) |
+               (vs2 << (sizeof(T) * 8 - rotate_amount));
+      });
+}
+
+// Helper function for testing the vror_vx instruction. Generate the expected
+// result by rotating the input bits right.
+template <typename T>
+inline void VrorVXHelper(RiscVVectorBasicBitManipulationTest *tester) {
+  tester->SetSemanticFunction(&Vror);
+  tester->BinaryOpTestHelperVV<T, T, T>(
+      absl::StrCat("Vror", sizeof(T) * 8, "vx"), /*sew*/ sizeof(T) * 8,
+      tester->instruction(), [](T vs2, T rs1) -> T {
+        T rotate_mask = sizeof(T) * 8 - 1;
+        T rotate_amount = (rs1 & rotate_mask);
+        return (vs2 >> rotate_amount) |
+               (vs2 << (sizeof(T) * 8 - rotate_amount));
+      });
+}
+
+// Helper function for testing the vror_vi instruction. Generate the expected
+// result by rotating the input bits right.
+template <typename T>
+inline void VrorVIHelper(RiscVVectorBasicBitManipulationTest *tester) {
+  tester->SetSemanticFunction(&Vror);
+  tester->BinaryOpTestHelperVV<T, T, T>(
+      absl::StrCat("Vror", sizeof(T) * 8, "vi"), /*sew*/ sizeof(T) * 8,
+      tester->instruction(), [](T vs2, T imm) -> T {
+        T rotate_mask = sizeof(T) * 8 - 1;
+        T rotate_amount = (imm & rotate_mask);
+        return (vs2 >> rotate_amount) |
+               (vs2 << (sizeof(T) * 8 - rotate_amount));
+      });
+}
+
+// Helper function for testing the vclz_v instruction. Generate the expected
+// result by counting the number of leading zeros in the input.
+template <typename T>
+inline void VclzVHelper(RiscVVectorBasicBitManipulationTest *tester) {
+  tester->SetSemanticFunction(&Vclz);
+  tester->UnaryOpTestHelperV<T, T>(
+      absl::StrCat("vclz", sizeof(T) * 8, "v"), /*sew*/ sizeof(T) * 8,
+      tester->instruction(), [](T vs2) -> T {
+        T mask = static_cast<T>(1) << (sizeof(T) * 8 - 1);
+        for (int i = 0; i < sizeof(T) * 8; ++i) {
+          if ((vs2 & mask) != 0) {
+            return i;
+          }
+          mask >>= 1;
+        }
+        return static_cast<T>(sizeof(T) * 8);
+      });
+}
+
+// Helper function for testing the vctz_v instruction. Generate the expected
+// result by counting the number of trailing zeros in the input.
+template <typename T>
+inline void VctzVHelper(RiscVVectorBasicBitManipulationTest *tester) {
+  tester->SetSemanticFunction(&Vctz);
+  tester->UnaryOpTestHelperV<T, T>(absl::StrCat("vctz", sizeof(T) * 8, "v"),
+                                   /*sew*/ sizeof(T) * 8, tester->instruction(),
+                                   [](T vs2) -> T {
+                                     T mask = static_cast<T>(1);
+                                     for (int i = 0; i < sizeof(T) * 8; ++i) {
+                                       if ((vs2 & mask) != 0) {
+                                         return i;
+                                       }
+                                       mask <<= 1;
+                                     }
+                                     return static_cast<T>(sizeof(T) * 8);
+                                   });
+}
+
+// Helper function for testing the vcpop_v instruction. Generate the expected
+// result by counting the number of bits set in the input.
+template <typename T>
+inline void VcpopVHelper(RiscVVectorBasicBitManipulationTest *tester) {
+  tester->SetSemanticFunction(&VectorVcpop);
+  tester->UnaryOpTestHelperV<T, T>(absl::StrCat("vcpop", sizeof(T) * 8, "v"),
+                                   /*sew*/ sizeof(T) * 8, tester->instruction(),
+                                   [](T vs2) -> T {
+                                     T result = 0;
+                                     for (int i = 0; i < sizeof(T) * 8; ++i) {
+                                       result += (vs2 & 1) ? 1 : 0;
+                                       vs2 >>= 1;
+                                     }
+                                     return result;
+                                   });
+}
+
+// Helper function for testing the vwsll_vv instruction. Generate the expected
+// result by shifting the widened input left.
+template <typename T>
+inline void VwsllVVHelper(RiscVVectorBasicBitManipulationTest *tester) {
+  using WT = typename WideType<T>::type;
+  tester->SetSemanticFunction(&Vwsll);
+  tester->BinaryOpTestHelperVV<WT, T, T>(
+      absl::StrCat("Vwsll", sizeof(T) * 8, "vv"), /*sew*/ sizeof(T) * 8,
+      tester->instruction(), [](T vs2, T vs1) -> WT {
+        T shift_mask = 2 * 8 * sizeof(T) - 1;
+        T shift_amount = (vs1 & shift_mask);
+        return static_cast<WT>(vs2) << shift_amount;
+      });
+}
+
+// Helper function for testing the vwsll_vx instruction. Generate the expected
+// result by shifting the widened input left.
+template <typename T>
+inline void VwsllVXHelper(RiscVVectorBasicBitManipulationTest *tester) {
+  using WT = typename WideType<T>::type;
+  tester->SetSemanticFunction(&Vwsll);
+  tester->BinaryOpTestHelperVV<WT, T, T>(
+      absl::StrCat("Vwsll", sizeof(T) * 8, "vx"), /*sew*/ sizeof(T) * 8,
+      tester->instruction(), [](T vs2, T rs1) -> WT {
+        T shift_mask = 2 * 8 * sizeof(T) - 1;
+        T shift_amount = (rs1 & shift_mask);
+        return static_cast<WT>(vs2) << shift_amount;
+      });
+}
+
+// Helper function for testing the vwsll_vi instruction. Generate the expected
+// result by shifting the widened input left.
+template <typename T>
+inline void VwsllVIHelper(RiscVVectorBasicBitManipulationTest *tester) {
+  using WT = typename WideType<T>::type;
+  tester->SetSemanticFunction(&Vwsll);
+  tester->BinaryOpTestHelperVV<WT, T, T>(
+      absl::StrCat("Vwsll", sizeof(T) * 8, "vi"), /*sew*/ sizeof(T) * 8,
+      tester->instruction(), [](T vs2, T imm) -> WT {
+        T shift_mask = 2 * 8 * sizeof(T) - 1;
+        T shift_amount = (imm & shift_mask);
+        return static_cast<WT>(vs2) << shift_amount;
+      });
+}
+
+TEST_F(RiscVVectorBasicBitManipulationTest, vandn) {
+  VandnVVHelper<uint8_t>(this);
+  ResetInstruction();
+  VandnVVHelper<uint16_t>(this);
+  ResetInstruction();
+  VandnVVHelper<uint32_t>(this);
+  ResetInstruction();
+  VandnVVHelper<uint64_t>(this);
+  ResetInstruction();
+
+  VandnVXHelper<uint8_t>(this);
+  ResetInstruction();
+  VandnVXHelper<uint16_t>(this);
+  ResetInstruction();
+  VandnVXHelper<uint32_t>(this);
+  ResetInstruction();
+  VandnVXHelper<uint64_t>(this);
+  ResetInstruction();
+}
+
+TEST_F(RiscVVectorBasicBitManipulationTest, vbrev8) {
+  Vbrev8VHelper<uint8_t>(this);
+  ResetInstruction();
+  Vbrev8VHelper<uint16_t>(this);
+  ResetInstruction();
+  Vbrev8VHelper<uint32_t>(this);
+  ResetInstruction();
+  Vbrev8VHelper<uint64_t>(this);
+  ResetInstruction();
+}
+
+TEST_F(RiscVVectorBasicBitManipulationTest, vrev8) {
+  Vrev8VHelper<uint8_t>(this);
+  ResetInstruction();
+  Vrev8VHelper<uint16_t>(this);
+  ResetInstruction();
+  Vrev8VHelper<uint32_t>(this);
+  ResetInstruction();
+  Vrev8VHelper<uint64_t>(this);
+  ResetInstruction();
+}
+
+TEST_F(RiscVVectorBasicBitManipulationTest, vrol) {
+  VrolVVHelper<uint8_t>(this);
+  ResetInstruction();
+  VrolVVHelper<uint16_t>(this);
+  ResetInstruction();
+  VrolVVHelper<uint32_t>(this);
+  ResetInstruction();
+  VrolVVHelper<uint64_t>(this);
+  ResetInstruction();
+
+  VrolVXHelper<uint8_t>(this);
+  ResetInstruction();
+  VrolVXHelper<uint16_t>(this);
+  ResetInstruction();
+  VrolVXHelper<uint32_t>(this);
+  ResetInstruction();
+  VrolVXHelper<uint64_t>(this);
+  ResetInstruction();
+}
+
+TEST_F(RiscVVectorBasicBitManipulationTest, vror) {
+  VrorVVHelper<uint8_t>(this);
+  ResetInstruction();
+  VrorVVHelper<uint16_t>(this);
+  ResetInstruction();
+  VrorVVHelper<uint32_t>(this);
+  ResetInstruction();
+  VrorVVHelper<uint64_t>(this);
+  ResetInstruction();
+
+  VrorVXHelper<uint8_t>(this);
+  ResetInstruction();
+  VrorVXHelper<uint16_t>(this);
+  ResetInstruction();
+  VrorVXHelper<uint32_t>(this);
+  ResetInstruction();
+  VrorVXHelper<uint64_t>(this);
+  ResetInstruction();
+
+  VrorVIHelper<uint8_t>(this);
+  ResetInstruction();
+  VrorVIHelper<uint16_t>(this);
+  ResetInstruction();
+  VrorVIHelper<uint32_t>(this);
+  ResetInstruction();
+  VrorVIHelper<uint64_t>(this);
+  ResetInstruction();
+}
+
+TEST_F(RiscVVectorBasicBitManipulationTest, vbrev) {
+  VbrevVHelper<uint8_t>(this);
+  ResetInstruction();
+  VbrevVHelper<uint16_t>(this);
+  ResetInstruction();
+  VbrevVHelper<uint32_t>(this);
+  ResetInstruction();
+  VbrevVHelper<uint64_t>(this);
+  ResetInstruction();
+}
+
+TEST_F(RiscVVectorBasicBitManipulationTest, vclzv) {
+  VclzVHelper<uint8_t>(this);
+  ResetInstruction();
+  VclzVHelper<uint16_t>(this);
+  ResetInstruction();
+  VclzVHelper<uint32_t>(this);
+  ResetInstruction();
+  VclzVHelper<uint64_t>(this);
+  ResetInstruction();
+}
+
+TEST_F(RiscVVectorBasicBitManipulationTest, vctz) {
+  VctzVHelper<uint8_t>(this);
+  ResetInstruction();
+  VctzVHelper<uint16_t>(this);
+  ResetInstruction();
+  VctzVHelper<uint32_t>(this);
+  ResetInstruction();
+  VctzVHelper<uint64_t>(this);
+  ResetInstruction();
+}
+
+TEST_F(RiscVVectorBasicBitManipulationTest, vcpop) {
+  VcpopVHelper<uint8_t>(this);
+  ResetInstruction();
+  VcpopVHelper<uint16_t>(this);
+  ResetInstruction();
+  VcpopVHelper<uint32_t>(this);
+  ResetInstruction();
+  VcpopVHelper<uint64_t>(this);
+  ResetInstruction();
+}
+
+TEST_F(RiscVVectorBasicBitManipulationTest, vwsll) {
+  VwsllVVHelper<uint8_t>(this);
+  ResetInstruction();
+  VwsllVVHelper<uint16_t>(this);
+  ResetInstruction();
+  VwsllVVHelper<uint32_t>(this);
+  ResetInstruction();
+
+  VwsllVXHelper<uint8_t>(this);
+  ResetInstruction();
+  VwsllVXHelper<uint16_t>(this);
+  ResetInstruction();
+  VwsllVXHelper<uint32_t>(this);
+  ResetInstruction();
+
+  VwsllVIHelper<uint8_t>(this);
+  ResetInstruction();
+  VwsllVIHelper<uint16_t>(this);
+  ResetInstruction();
+  VwsllVIHelper<uint32_t>(this);
+  ResetInstruction();
+}
+
+}  // namespace
diff --git a/riscv/test/zvbb_encoding_test.cc b/riscv/test/zvbb_encoding_test.cc
new file mode 100644
index 0000000..64f7be6
--- /dev/null
+++ b/riscv/test/zvbb_encoding_test.cc
@@ -0,0 +1,203 @@
+// 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/zvbb_encoding.h"
+
+#include <cstdint>
+
+#include "googlemock/include/gmock/gmock.h"
+#include "mpact/sim/generic/type_helpers.h"
+#include "mpact/sim/util/memory/flat_demand_memory.h"
+#include "riscv/riscv_state.h"
+#include "riscv/zvbb_enums.h"
+
+// This file contains tests for the RiscV32GZBEncoding class to ensure that
+// the instruction decoding is correct.
+
+namespace {
+
+using ::mpact::sim::generic::operator*;  // NOLINT: clang-tidy false positive.
+
+using mpact::sim::riscv::RiscVState;
+using mpact::sim::riscv::RiscVXlen;
+using mpact::sim::util::FlatDemandMemory;
+
+using mpact::sim::riscv::zvbb::kComplexResourceNames;
+using mpact::sim::riscv::zvbb::kDestOpNames;
+using mpact::sim::riscv::zvbb::kSimpleResourceNames;
+using mpact::sim::riscv::zvbb::kSourceOpNames;
+
+using SlotEnum = mpact::sim::riscv::zvbb::SlotEnum;
+using OpcodeEnum = mpact::sim::riscv::zvbb::OpcodeEnum;
+using SourceOpEnum = mpact::sim::riscv::zvbb::SourceOpEnum;
+using DestOpEnum = mpact::sim::riscv::zvbb::DestOpEnum;
+using SimpleResourceEnum = mpact::sim::riscv::zvbb::SimpleResourceEnum;
+using ComplexResourceEnum = mpact::sim::riscv::zvbb::ComplexResourceEnum;
+
+using mpact::sim::riscv::zvbb::ZVBBEncoding;
+
+// Constexpr for opcodes for vector basic bit manipulation instructions.
+constexpr uint32_t kVandnVv = 0b000001'0'00000'00000'000'00000'1010111;
+constexpr uint32_t kVandnVx = 0b000001'0'00000'00000'100'00000'1010111;
+constexpr uint32_t kVbrev8V = 0b010010'0'00000'01000'010'00000'1010111;
+constexpr uint32_t kVrev8V = 0b010010'0'00000'01001'010'00000'1010111;
+constexpr uint32_t kVrolVv = 0b010101'0'00000'00000'000'00000'1010111;
+constexpr uint32_t kVrolVx = 0b010101'0'00000'00000'100'00000'1010111;
+constexpr uint32_t kVrorVv = 0b010100'0'00000'00000'000'00000'1010111;
+constexpr uint32_t kVrorVx = 0b010100'0'00000'00000'100'00000'1010111;
+constexpr uint32_t kVrorVi_i5_0 = 0b01010'0'0'00000'00000'011'00000'1010111;
+constexpr uint32_t kVrorVi_i5_1 = 0b01010'1'0'00000'00000'011'00000'1010111;
+
+constexpr uint32_t kVbrevV = 0b010010'0'00000'01010'010'00000'1010111;
+constexpr uint32_t kVclzV = 0b010010'0'00000'01100'010'00000'1010111;
+constexpr uint32_t kVctzV = 0b010010'0'00000'01101'010'00000'1010111;
+constexpr uint32_t kVcpopV = 0b010010'0'00000'01110'010'00000'1010111;
+constexpr uint32_t kVwsllVv = 0b110101'0'00000'00000'000'00000'1010111;
+constexpr uint32_t kVwsllVx = 0b110101'0'00000'00000'100'00000'1010111;
+constexpr uint32_t kVwsllVi = 0b110101'0'00000'00000'011'00000'1010111;
+
+class ZVBBEncodingTest : public testing::Test {
+ protected:
+  ZVBBEncodingTest() {
+    state_ = new RiscVState("test", RiscVXlen::RV32, &memory_);
+    enc_ = new ZVBBEncoding(state_);
+  }
+
+  ~ZVBBEncodingTest() override {
+    delete enc_;
+    delete state_;
+  }
+
+  FlatDemandMemory memory_;
+  RiscVState *state_;
+  ZVBBEncoding *enc_;
+};
+
+TEST_F(ZVBBEncodingTest, 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(ZVBBEncodingTest, 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] << ")";
+  }
+}
+
+TEST_F(ZVBBEncodingTest, 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(ZVBBEncodingTest, 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(ZVBBEncodingTest, VandnVv) {
+  enc_->ParseInstruction(kVandnVv);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscvZvbb, 0), OpcodeEnum::kVandnVv);
+}
+
+TEST_F(ZVBBEncodingTest, VandnVx) {
+  enc_->ParseInstruction(kVandnVx);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscvZvbb, 0), OpcodeEnum::kVandnVx);
+}
+
+TEST_F(ZVBBEncodingTest, Vbrev8V) {
+  enc_->ParseInstruction(kVbrev8V);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscvZvbb, 0), OpcodeEnum::kVbrev8V);
+}
+
+TEST_F(ZVBBEncodingTest, Vrev8V) {
+  enc_->ParseInstruction(kVrev8V);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscvZvbb, 0), OpcodeEnum::kVrev8V);
+}
+
+TEST_F(ZVBBEncodingTest, VrolVv) {
+  enc_->ParseInstruction(kVrolVv);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscvZvbb, 0), OpcodeEnum::kVrolVv);
+}
+
+TEST_F(ZVBBEncodingTest, VrolVx) {
+  enc_->ParseInstruction(kVrolVx);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscvZvbb, 0), OpcodeEnum::kVrolVx);
+}
+
+TEST_F(ZVBBEncodingTest, VrorVv) {
+  enc_->ParseInstruction(kVrorVv);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscvZvbb, 0), OpcodeEnum::kVrorVv);
+}
+
+TEST_F(ZVBBEncodingTest, VrorVx) {
+  enc_->ParseInstruction(kVrorVx);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscvZvbb, 0), OpcodeEnum::kVrorVx);
+}
+
+TEST_F(ZVBBEncodingTest, VrorVi) {
+  enc_->ParseInstruction(kVrorVi_i5_0);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscvZvbb, 0), OpcodeEnum::kVrorVi);
+  enc_->ParseInstruction(kVrorVi_i5_1);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscvZvbb, 0), OpcodeEnum::kVrorVi);
+}
+
+TEST_F(ZVBBEncodingTest, VbrevV) {
+  enc_->ParseInstruction(kVbrevV);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscvZvbb, 0), OpcodeEnum::kVbrevV);
+}
+
+TEST_F(ZVBBEncodingTest, VclzV) {
+  enc_->ParseInstruction(kVclzV);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscvZvbb, 0), OpcodeEnum::kVclzV);
+}
+
+TEST_F(ZVBBEncodingTest, VctzV) {
+  enc_->ParseInstruction(kVctzV);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscvZvbb, 0), OpcodeEnum::kVctzV);
+}
+
+TEST_F(ZVBBEncodingTest, VcpopV) {
+  enc_->ParseInstruction(kVcpopV);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscvZvbb, 0), OpcodeEnum::kVcpopV);
+}
+
+TEST_F(ZVBBEncodingTest, VwsllVv) {
+  enc_->ParseInstruction(kVwsllVv);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscvZvbb, 0), OpcodeEnum::kVwsllVv);
+}
+
+TEST_F(ZVBBEncodingTest, VwsllVx) {
+  enc_->ParseInstruction(kVwsllVx);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscvZvbb, 0), OpcodeEnum::kVwsllVx);
+}
+
+TEST_F(ZVBBEncodingTest, VwsllVi) {
+  enc_->ParseInstruction(kVwsllVi);
+  EXPECT_EQ(enc_->GetOpcode(SlotEnum::kRiscvZvbb, 0), OpcodeEnum::kVwsllVi);
+}
+
+}  // namespace
diff --git a/riscv/zvbb_encoding.cc b/riscv/zvbb_encoding.cc
new file mode 100644
index 0000000..3ac6038
--- /dev/null
+++ b/riscv/zvbb_encoding.cc
@@ -0,0 +1,130 @@
+// 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/zvbb_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_getters_zvbb.h"
+#include "riscv/riscv_register.h"
+#include "riscv/riscv_state.h"
+#include "riscv/zvbb_bin_decoder.h"
+#include "riscv/zvbb_decoder.h"
+#include "riscv/zvbb_enums.h"
+
+namespace mpact::sim::riscv::zvbb {
+
+using ::mpact::sim::generic::operator*;  // NOLINT: clang-tidy false positive.
+
+ZVBBEncoding::ZVBBEncoding(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; });
+
+  AddRiscVZvbbSourceVectorGetters<SourceOpEnum, Extractors, RVVectorRegister>(
+      source_op_getters_, this);
+  AddRiscvZvbbSourceScalarGetters<SourceOpEnum, Extractors, RV32Register>(
+      source_op_getters_, this);
+
+  AddRiscVZvbbDestGetters<DestOpEnum, Extractors, RVVectorRegister>(
+      dest_op_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;
+    }
+  }
+}
+
+ZVBBEncoding::~ZVBBEncoding() { delete resource_pool_; }
+
+void ZVBBEncoding::ParseInstruction(uint32_t inst_word) {
+  inst_word_ = inst_word;
+  auto [opcode, format] = DecodeRiscVZvbbInst32WithFormat(inst_word_);
+  opcode_ = opcode;
+  format_ = format;
+}
+
+ResourceOperandInterface *ZVBBEncoding::GetComplexResourceOperand(
+    SlotEnum, int, OpcodeEnum, ComplexResourceEnum resource, int begin,
+    int end) {
+  return nullptr;
+}
+
+ResourceOperandInterface *ZVBBEncoding::GetSimpleResourceOperand(
+    SlotEnum, int, OpcodeEnum, SimpleResourceVector &resource_vec, int end) {
+  return nullptr;
+}
+
+DestinationOperandInterface *ZVBBEncoding::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 *ZVBBEncoding::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::zvbb
diff --git a/riscv/zvbb_encoding.h b/riscv/zvbb_encoding.h
new file mode 100644
index 0000000..bc84053
--- /dev/null
+++ b/riscv/zvbb_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_ZVBB_ENCODING_H_
+#define THIRD_PARTY_MPACT_RISCV_ZVBB_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/zvbb_bin_decoder.h"
+#include "riscv/zvbb_decoder.h"
+#include "riscv/zvbb_enums.h"
+
+namespace mpact::sim::riscv::zvbb {
+
+// 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 ZVBBEncoding : public ZVBBEncodingBase, public RiscVEncodingCommon {
+ public:
+  explicit ZVBBEncoding(RiscVState *state);
+  ~ZVBBEncoding() 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::zvbb
+
+#endif  // THIRD_PARTY_MPACT_RISCV_ZVBB_ENCODING_H_