Merge pull request #7 from akihikodaki:template

PiperOrigin-RevId: 718087600
Change-Id: I935e8de167bb5118acffc0b5818b80b595dbe66a
diff --git a/repos.bzl b/repos.bzl
index 0ec585f..b413290 100644
--- a/repos.bzl
+++ b/repos.bzl
@@ -22,7 +22,7 @@
     if not native.existing_rule("com_google_mpact-sim"):
         http_archive(
             name = "com_google_mpact-sim",
-            sha256 = "99d29bebd3ebccdec699ae3448a03f1245778e3f7a9abae9dcc9770aa51da287",
-            strip_prefix = "mpact-sim-cd146315f0214f1ecaeadeaecadb12c6f9557cb6",
-            url = "https://github.com/google/mpact-sim/archive/cd146315f0214f1ecaeadeaecadb12c6f9557cb6.tar.gz",
+            sha256 = "10bd6f43f6d340535f0eef15bb946c8332e62148458a2dc3b6a412739b4cf253",
+            strip_prefix = "mpact-sim-d958141f3e6f8dfbdc79e35a9beabaea490e479c",
+            url = "https://github.com/google/mpact-sim/archive/d958141f3e6f8dfbdc79e35a9beabaea490e479c.tar.gz",
         )
diff --git a/riscv/BUILD b/riscv/BUILD
index fd00819..65faa21 100644
--- a/riscv/BUILD
+++ b/riscv/BUILD
@@ -268,6 +268,7 @@
         "@com_google_absl//absl/status",
         "@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",
@@ -307,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",
@@ -605,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 = [
@@ -643,6 +697,7 @@
         "riscv_getters_zba.h",
         "riscv_getters_zbb32.h",
         "riscv_getters_zbb64.h",
+        "riscv_getters_zvbb.h",
     ],
     deps = [
         ":riscv_encoding_common",
@@ -807,6 +862,56 @@
 )
 
 cc_library(
+    name = "riscv64g_encoder",
+    srcs = [
+        "riscv64g_bin_encoder_interface.cc",
+        "riscv_bin_setters.cc",
+    ],
+    hdrs = [
+        "riscv64g_bin_encoder_interface.h",
+        "riscv_bin_setters.h",
+    ],
+    deps = [
+        ":riscv64g_bin_fmt",
+        ":riscv64g_isa",
+        ":riscv_getters",
+        ":riscv_state",
+        "@com_google_absl//absl/base:no_destructor",
+        "@com_google_absl//absl/container:flat_hash_map",
+        "@com_google_absl//absl/functional:any_invocable",
+        "@com_google_absl//absl/log:check",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/status:statusor",
+        "@com_google_absl//absl/strings",
+        "@com_google_mpact-sim//mpact/sim/generic:type_helpers",
+        "@com_google_mpact-sim//mpact/sim/util/asm",
+        "@com_googlesource_code_re2//:re2",
+    ],
+)
+
+cc_binary(
+    name = "riscv64g_as",
+    srcs = ["riscv64g_as_main.cc"],
+    copts = ["-O3"],
+    deps = [
+        ":riscv64g_bin_fmt",
+        ":riscv64g_encoder",
+        ":riscv64g_isa",
+        "@com_github_serge1_elfio//:elfio",
+        "@com_google_absl//absl/flags:flag",
+        "@com_google_absl//absl/flags:parse",
+        "@com_google_absl//absl/log",
+        "@com_google_absl//absl/log:check",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/strings",
+        "@com_google_mpact-sim//mpact/sim/generic:type_helpers",
+        "@com_google_mpact-sim//mpact/sim/util/asm",
+        "@com_google_mpact-sim//mpact/sim/util/asm:simple_assembler",
+        "@com_googlesource_code_re2//:re2",
+    ],
+)
+
+cc_library(
     name = "riscv64g_vec_decoder",
     srcs = [
         "riscv64g_vec_decoder.cc",
@@ -871,6 +976,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",
@@ -1208,6 +1348,27 @@
     ],
 )
 
+cc_library(
+    name = "riscv_plic",
+    srcs = [
+        "riscv_plic.cc",
+    ],
+    hdrs = [
+        "riscv_plic.h",
+    ],
+    deps = [
+        "@com_google_absl//absl/container:btree",
+        "@com_google_absl//absl/log",
+        "@com_google_absl//absl/numeric:bits",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/strings",
+        "@com_google_mpact-sim//mpact/sim/generic:core",
+        "@com_google_mpact-sim//mpact/sim/generic:instruction",
+        "@com_google_mpact-sim//mpact/sim/util/memory",
+        "@com_googlesource_code_re2//:re2",
+    ],
+)
+
 cc_binary(
     name = "rv32g_test_sim",
     srcs = [
diff --git a/riscv/riscv64g.isa b/riscv/riscv64g.isa
index 7fcbc7f..467e57c 100644
--- a/riscv/riscv64g.isa
+++ b/riscv/riscv64g.isa
@@ -43,7 +43,7 @@
 slot riscv64g : riscv64i, riscv64c, riscv64m, riscv64_amo_arithmetic, riscv64f, riscv64d, zicsr, zfencei, privileged {
   default size = 4;
   default opcode =
-    disasm: "Illegal instruction at 0x%(@:08x)",
+    disasm: "Illegal instruction at %(@:08x)",
     semfunc: "&RiscVIllegalInstruction";
 }
 
@@ -57,11 +57,11 @@
   resources TwoOp = { next_pc, rs1 : rd[..rd]};
   resources ThreeOp = { next_pc, rs1, rs2 : rd[..rd]};
   opcodes {
-    addi{: rs1, I_imm12 : rd},
+    addi{: rs1, %reloc(I_imm12) : rd},
       resources: TwoOp,
       disasm: "addi", "%rd, %rs1, %I_imm12",
       semfunc: "&RV64::RiscVIAdd";
-    addiw{: rs1, I_imm12 : rd},
+    addiw{: rs1, %reloc(I_imm12) : rd},
       resources: TwoOp,
       disasm: "addiw", "%rd, %rs1, %I_imm12",
       semfunc: "&RV64::RiscVIAddw";
@@ -87,42 +87,42 @@
       semfunc: "&RV64::RiscVIXor";
     slli{: rs1, I_uimm6 : rd},
       resources: TwoOp,
-      disasm: "slli", "%rd, %rs1, 0x%(I_uimm6:x)",
+      disasm: "slli", "%rd, %rs1, %(I_uimm6:x)",
       semfunc: "&RV64::RiscVISll";
     srli{: rs1, I_uimm6 : rd},
       resources: TwoOp,
-      disasm: "srli", "%rd  %rs1, 0x%(I_uimm6:x)",
+      disasm: "srli", "%rd  %rs1, %(I_uimm6:x)",
       semfunc: "&RV64::RiscVISrl";
     srai{: rs1, I_uimm6 : rd},
       resources: TwoOp,
-      disasm: "srai", "%rd, %rs1, 0x%(I_uimm6:x)",
+      disasm: "srai", "%rd, %rs1, %(I_uimm6:x)",
       semfunc: "&RV64::RiscVISra";
     slliw{: rs1, I_uimm5 : rd},
       resources: TwoOp,
-      disasm: "slliw", "%rd, %rs1, 0x%(I_uimm5:x)",
+      disasm: "slliw", "%rd, %rs1, %(I_uimm5:x)",
       semfunc: "&RV64::RiscVISllw";
     srliw{: rs1, I_uimm5 : rd},
       resources: TwoOp,
-      disasm: "srliw", "%rd  %rs1, 0x%(I_uimm5:x)",
+      disasm: "srliw", "%rd  %rs1, %(I_uimm5:x)",
       semfunc: "&RV64::RiscVISrlw";
     sraiw{: rs1, I_uimm5 : rd},
       resources: TwoOp,
-      disasm: "sraiw", "%rd, %rs1, 0x%(I_uimm5:x)",
+      disasm: "sraiw", "%rd, %rs1, %(I_uimm5:x)",
       semfunc: "&RV64::RiscVISraw";
-    lui{: U_imm20 : rd},
+    lui{: %reloc(U_imm20) : rd},
       resources: { next_pc : rd[0..]},
-      disasm: "lui", "%rd, 0x%(U_imm20:08x)",
+      disasm: "lui", "%rd, %(U_imm20:08x)",
       semfunc: "&RV64::RiscVILui";
-    auipc{: U_imm20 : rd},
+    auipc{: %reloc(U_imm20) : rd},
       resources: { next_pc : rd[0..]},
-      disasm: "auipc", "%rd, 0x%(U_imm20:08x)",
+      disasm: "auipc", "%rd, %(U_imm20:08x)",
       semfunc: "&RV64::RiscVIAuipc";
     add{: rs1, rs2 : rd},
       resources: ThreeOp,
       disasm: "add", "%rd, %rs1, %rs2",
       semfunc: "&RV64::RiscVIAdd";
     addw{: rs1, rs2 : rd},
-      disasm: "add", "%rd, %rs1, %rs2",
+      disasm: "addw", "%rd, %rs1, %rs2",
       semfunc: "&RV64::RiscVIAddw";
     slt{: rs1, rs2 : rd},
       resources: ThreeOp,
@@ -170,7 +170,7 @@
       semfunc: "&RV64::RiscVISrlw";
     subw{: rs1, rs2 : rd},
       resources: ThreeOp,
-      disasm: "sub", "%rd, %rs1, %rs2",
+      disasm: "subw", "%rd, %rs1, %rs2",
       semfunc: "&RV64::RiscVISubw";
     sraw{: rs1, rs2 : rd},
       resources: ThreeOp,
@@ -182,87 +182,87 @@
     hint{},
       disasm: "hint",
       semfunc: "&RiscVINop";
-    jal{: J_imm20 : next_pc, rd},
+    jal{: %reloc(J_imm20) : next_pc, rd},
       resources: { next_pc : next_pc[0..], rd[0..]},
       disasm: "jal", "%rd, %(@+J_imm20:08x)",
       semfunc: "&RV64::RiscVIJal";
-    jalr{: rs1, J_imm12 : next_pc, rd},
+    jalr{: rs1, %reloc(J_imm12) : next_pc, rd},
       resources: { next_pc, rs1 : next_pc[0..], rd[0..]},
       disasm: "jalr", "%rd, %rs1, %J_imm12",
       semfunc: "&RV64::RiscVIJalr";
-    j{: J_imm20 : next_pc, rd},
+    j{: %reloc(J_imm20) : next_pc, rd},
       resources: { next_pc : next_pc[0..], rd[0..]},
       disasm: "j", "%(@+J_imm20:08x)",
       semfunc: "&RV64::RiscVIJal";
-    jr{: rs1, J_imm12 : next_pc, rd},
+    jr{: rs1, %reloc(J_imm12) : next_pc, rd},
       resources: { next_pc, rs1 : next_pc[0..], rd[0..]},
-      disasm: "jr", "%rs1, %J_imm12",
+      disasm: "jr", "%rs1, %(J_imm12:08x)",
       semfunc: "&RV64::RiscVIJalr";
-    beq{: rs1, rs2, B_imm12 : next_pc},
+    beq{: rs1, rs2, %reloc(B_imm12) : next_pc},
       resources: { next_pc, rs1, rs2 : next_pc[0..]},
       disasm: "beq", "%rs1, %rs2, %(@+B_imm12:08x)",
       semfunc: "&RV64::RiscVIBeq";
-    bne{: rs1, rs2, B_imm12 : next_pc},
+    bne{: rs1, rs2, %reloc(B_imm12) : next_pc},
       resources: { next_pc, rs1, rs2 : next_pc[0..]},
       disasm: "bne", "%rs1, %rs2, %(@+B_imm12:08x)",
       semfunc: "&RV64::RiscVIBne";
-    blt{: rs1, rs2, B_imm12 : next_pc},
+    blt{: rs1, rs2, %reloc(B_imm12) : next_pc},
       resources: { next_pc, rs1, rs2 : next_pc[0..]},
       disasm: "blt", "%rs1, %rs2, %(@+B_imm12:08x)",
       semfunc: "&RV64::RiscVIBlt";
-    bltu{: rs1, rs2, B_imm12 : next_pc},
+    bltu{: rs1, rs2, %reloc(B_imm12) : next_pc},
       resources: { next_pc, rs1, rs2 : next_pc[0..]},
       disasm: "bltu", "%rs1, %rs2, %(@+B_imm12:08x)",
       semfunc: "&RV64::RiscVIBltu";
-    bge{: rs1, rs2, B_imm12 : next_pc},
+    bge{: rs1, rs2, %reloc(B_imm12) : next_pc},
       resources: { next_pc, rs1, rs2 : next_pc[0..]},
       disasm: "bge", "%rs1, %rs2, %(@+B_imm12:08x)",
       semfunc: "&RV64::RiscVIBge";
-    bgeu{: rs1, rs2, B_imm12 : next_pc},
+    bgeu{: rs1, rs2, %reloc(B_imm12) : next_pc},
       resources: { next_pc, rs1, rs2 : next_pc[0..]},
       disasm: "bgeu", "%rs1, %rs2, %(@+B_imm12:08x)",
       semfunc: "&RV64::RiscVIBgeu";
-    ld{(: rs1, I_imm12), (: : rd)},
+    ld{(: rs1, %reloc(I_imm12)), (: : rd)},
       resources: { next_pc, rs1 : rd[0..]},
       disasm: "ld", "%rd, %I_imm12(%rs1)",
       semfunc: "&RV64::RiscVILd", "&RV64::RiscVILdChild";
-    lw{(: rs1, I_imm12), (: : rd)},
+    lw{(: rs1, %reloc(I_imm12)), (: : rd)},
       resources: { next_pc, rs1 : rd[0..]},
       disasm: "lw", "%rd, %I_imm12(%rs1)",
       semfunc: "&RV64::RiscVILw", "&RV64::RiscVILwChild";
-    lwu{(: rs1, I_imm12), (: : rd)},
+    lwu{(: rs1, %reloc(I_imm12)), (: : rd)},
       resources: { next_pc, rs1 : rd[0..]},
       disasm: "lwu", "%rd, %I_imm12(%rs1)",
       semfunc: "&RV64::RiscVILw", "&RV64::RiscVILwuChild";
-    lh{(: rs1, I_imm12 :), (: : rd)},
+    lh{(: rs1, %reloc(I_imm12) :), (: : rd)},
       resources: { next_pc, rs1 : rd[0..]},
       disasm: "lh", "%rd, %I_imm12(%rs1)",
       semfunc: "&RV64::RiscVILh", "&RV64::RiscVILhChild";
-    lhu{(: rs1, I_imm12 :), (: : rd)},
+    lhu{(: rs1, %reloc(I_imm12) :), (: : rd)},
       resources: { next_pc, rs1 : rd[0..]},
       disasm: "lhu", "%rd, %I_imm12(%rs1)",
       semfunc: "&RV64::RiscVILhu", "&RV64::RiscVILhuChild";
-    lb{(: rs1, I_imm12 :), (: : rd)},
+    lb{(: rs1, %reloc(I_imm12) :), (: : rd)},
       resources: { next_pc, rs1 : rd[0..]},
       disasm: "lb", "%rd, %I_imm12(%rs1)",
       semfunc: "&RV64::RiscVILb", "&RV64::RiscVILbChild";
-    lbu{(: rs1, I_imm12 :), (: : rd)},
+    lbu{(: rs1, %reloc(I_imm12) :), (: : rd)},
       resources: { next_pc, rs1 : rd[0..]},
       disasm: "lbu", "%rd, %I_imm12(%rs1)",
       semfunc: "&RV64::RiscVILbu", "&RV64::RiscVILbuChild";
-    sd{: rs1, S_imm12, rs2 : },
+    sd{: rs1, %reloc(S_imm12), rs2 : },
       resources: { next_pc, rs1, rs2 : },
       disasm: "sd", "%rs2, %S_imm12(%rs1)",
       semfunc: "&RV64::RiscVISd";
-    sw{: rs1, S_imm12, rs2 : },
+    sw{: rs1, %reloc(S_imm12), rs2 : },
       resources: { next_pc, rs1, rs2 : },
       disasm: "sw", "%rs2, %S_imm12(%rs1)",
       semfunc: "&RV64::RiscVISw";
-    sh{: rs1, S_imm12, rs2 : },
+    sh{: rs1, %reloc(S_imm12), rs2 : },
       resources: { next_pc, rs1, rs2 : },
       disasm: "sh", "%rs2, %S_imm12(%rs1)",
       semfunc: "&RV64::RiscVISh";
-    sb{: rs1, S_imm12, rs2 : },
+    sb{: rs1, %reloc(S_imm12), rs2 : },
       resources: { next_pc, rs1, rs2 : },
       disasm: "sb", "%rs2, %S_imm12(%rs1)",
       semfunc: "&RV64::RiscVISb";
@@ -442,11 +442,11 @@
   opcodes {
     amoswapw{(: rs1, rs2, A_aq, A_rl), (: : rd)},
       resources: ThreeOp,
-      disasm: "amoswap.w", "%rd, rs2, (%rs1)",
+      disasm: "amoswap.w", "%rd, %rs2, (%rs1)",
       semfunc: "&AAmoswapw", "&RV64::RiscVILwChild";
     amoswapd{(: rs1, rs2, A_aq, A_rl), (: : rd)},
       resources: ThreeOp,
-      disasm: "amoswap.d", "%rd, rs2, (%rs1)",
+      disasm: "amoswap.d", "%rd, %rs2, (%rs1)",
       semfunc: "&AAmoswapd", "&RV64::RiscVILdChild";
   }
 }
@@ -460,27 +460,27 @@
   opcodes {
     amoandw{(: rs1, rs2, A_aq, A_rl), (: : rd)},
       resources: ThreeOp,
-      disasm: "amoand.w", "%rd, rs2, (%rs1)",
+      disasm: "amoand.w%A_aq%A_rl", "%rd, %rs2, (%rs1)",
       semfunc: "&AAmoandw", "&RV64::RiscVILwChild";
     amoandd{(: rs1, rs2, A_aq, A_rl), (: : rd)},
       resources: ThreeOp,
-      disasm: "amoand.d", "%rd, rs2, (%rs1)",
+      disasm: "amoand.d%A_aq%A_rl", "%rd, %rs2, (%rs1)",
       semfunc: "&AAmoandd", "&RV64::RiscVILdChild";
     amoorw{(: rs1, rs2, A_aq, A_rl), (: : rd)},
       resources: ThreeOp,
-      disasm: "amoor.w", "%rd, rs2, (%rs1)",
+      disasm: "amoor.w", "%rd, %rs2, (%rs1)",
       semfunc: "&AAmoorw", "&RV64::RiscVILwChild";
     amoord{(: rs1, rs2, A_aq, A_rl), (: : rd)},
       resources: ThreeOp,
-      disasm: "amoor.d", "%rd, rs2, (%rs1)",
+      disasm: "amoor.d", "%rd, %rs2, (%rs1)",
       semfunc: "&AAmoord", "&RV64::RiscVILdChild";
     amoxorw{(: rs1, rs2, A_aq, A_rl), (: : rd)},
       resources: ThreeOp,
-      disasm: "amoxor.w", "%rd, rs2, (%rs1)",
+      disasm: "amoxor.w", "%rd, %rs2, (%rs1)",
       semfunc: "&AAmoxorw", "&RV64::RiscVILwChild";
     amoxord{(: rs1, rs2, A_aq, A_rl), (: : rd)},
       resources: ThreeOp,
-      disasm: "amoxor.d", "%rd, rs2, (%rs1)",
+      disasm: "amoxor.d", "%rd, %rs2, (%rs1)",
       semfunc: "&AAmoxord", "&RV64::RiscVILdChild";
   }
 }
@@ -494,43 +494,43 @@
   opcodes {
     amoaddw{(: rs1, rs2, A_aq, A_rl), (: : rd)},
       resources: ThreeOp,
-      disasm: "amoadd.w", "%rd, rs2, (%rs1)",
+      disasm: "amoadd.w", "%rd, %rs2, (%rs1)",
       semfunc: "&AAmoaddw", "&RV64::RiscVILwChild";
     amoaddd{(: rs1, rs2, A_aq, A_rl), (: : rd)},
       resources: ThreeOp,
-      disasm: "amoadd.d", "%rd, rs2, (%rs1)",
+      disasm: "amoadd.d", "%rd, %rs2, (%rs1)",
       semfunc: "&AAmoaddd", "&RV64::RiscVILdChild";
     amomaxw{(: rs1, rs2, A_aq, A_rl), (: : rd)},
       resources: ThreeOp,
-      disasm: "amomax.w", "%rd, rs2, (%rs1)",
+      disasm: "amomax.w", "%rd, %rs2, (%rs1)",
       semfunc: "&AAmomaxw", "&RV64::RiscVILwChild";
     amomaxd{(: rs1, rs2, A_aq, A_rl), (: : rd)},
       resources: ThreeOp,
-      disasm: "amomax.d", "%rd, rs2, (%rs1)",
+      disasm: "amomax.d", "%rd, %rs2, (%rs1)",
       semfunc: "&AAmomaxd", "&RV64::RiscVILdChild";
     amomaxuw{(: rs1, rs2, A_aq, A_rl), (: : rd)},
       resources: ThreeOp,
-      disasm: "amomaxu.w", "%rd, rs2, (%rs1)",
+      disasm: "amomaxu.w", "%rd, %rs2, (%rs1)",
       semfunc: "&AAmomaxuw", "&RV64::RiscVILwChild";
     amomaxud{(: rs1, rs2, A_aq, A_rl), (: : rd)},
       resources: ThreeOp,
-      disasm: "amomaxu.d", "%rd, rs2, (%rs1)",
+      disasm: "amomaxu.d", "%rd, %rs2, (%rs1)",
       semfunc: "&AAmomaxud", "&RV64::RiscVILdChild";
     amominw{(: rs1, rs2, A_aq, A_rl), (: : rd)},
       resources: ThreeOp,
-      disasm: "amomin.w", "%rd, rs2, (%rs1)",
+      disasm: "amomin.w", "%rd, %rs2, (%rs1)",
       semfunc: "&AAmominw", "&RV64::RiscVILwChild";
     amomind{(: rs1, rs2, A_aq, A_rl), (: : rd)},
       resources: ThreeOp,
-      disasm: "amomin.d", "%rd, rs2, (%rs1)",
+      disasm: "amomin.d", "%rd, %rs2, (%rs1)",
       semfunc: "&AAmomind", "&RV64::RiscVILdChild";
     amominuw{(: rs1, rs2, A_aq, A_rl), (: : rd)},
       resources: ThreeOp,
-      disasm: "amominu.w", "%rd, rs2, (%rs1)",
+      disasm: "amominu.w", "%rd, %rs2, (%rs1)",
       semfunc: "&AAmominuw", "&RV64::RiscVILwChild";
     amominud{(: rs1, rs2, A_aq, A_rl), (: : rd)},
       resources: ThreeOp,
-      disasm: "amominu.d", "%rd, rs2, (%rs1)",
+      disasm: "amominu.d", "%rd, %rs2, (%rs1)",
       semfunc: "&AAmominud", "&RV64::RiscVILdChild";
   }
 }
@@ -621,11 +621,11 @@
   resources ThreeOp = { next_pc, frs1, frs2 : frd[0..]};
   resources FourOp = { next_pc, frs1, frs2, frs3 : frd[0..]};
   opcodes {
-    flw{(: rs1, I_imm12 : ), (: : frd)},
+    flw{(: rs1, %reloc(I_imm12) : ), (: : frd)},
       resources: { next_pc, rs1 : frd[0..]},
       semfunc: "&RV64::RiscVILw", "&RiscVIFlwChild",
       disasm: "flw", "%frd, %I_imm12(%rs1)";
-    fsw{: rs1, S_imm12, frs2},
+    fsw{: rs1, %reloc(S_imm12), frs2},
       resources: { next_pc, rs1, frs2},
       semfunc: "&RV64::RiscVFSw",
       disasm: "fsw", "%frs2, %S_imm12(%rs1)";
@@ -752,11 +752,11 @@
   default size = 4;
   default latency = global_latency;
   opcodes {
-    fld{(: rs1, I_imm12 : ), (: : drd)},
+    fld{(: rs1, %reloc(I_imm12) : ), (: : drd)},
       resources: {next_pc, rs1 : drd[0..]},
       semfunc: "&RV64::RiscVILd", "&RV64::RiscVILdChild",
       disasm: "fld", "%drd, %I_imm12(%rs1)";
-    fsd{: rs1, S_imm12, drs2},
+    fsd{: rs1, %reloc(S_imm12), drs2},
       resources: {next_pc, rs1, drs2},
       semfunc: "&RV64::RiscVDSd",
       disasm: "fsd", "%drs2, %S_imm12(%rs1)";
@@ -936,7 +936,7 @@
       resources: {next_pc,c3rs1, c3drs2},
       disasm: "c.fsd", "%c3drs2, %I_cl_uimm5x8(%c3rs1)",
       semfunc: "&RV64::RiscVISd";
-    cj{: I_cj_imm11, x0 : next_pc, x0},
+    cj{: %reloc(I_cj_imm11), x0 : next_pc, x0},
       resources: {next_pc,x0 : next_pc[0..], x0[0..]},
       disasm: "c.j", "%(@+I_cj_imm11:08x)",
       semfunc: "&RV64::RiscVIJal";
@@ -948,11 +948,11 @@
       resources: {next_pc,crs1, x0 : next_pc[0..], x1[0..]},
       disasm: "c.jalr", "%crs1",
       semfunc: "&RV64::RiscVIJalr";
-    cbeqz{: c3rs1, x0, I_cb_imm8 : next_pc},
+    cbeqz{: c3rs1, x0, %reloc(I_cb_imm8) : next_pc},
       resources: {next_pc,c3rs1, x0 : next_pc[0..]},
       disasm: "c.beqz", "%c3rs1, %(@+I_cb_imm8:08x)",
       semfunc: "&RV64::RiscVIBeq";
-    cbnez{: c3rs1, x0, I_cb_imm8 : next_pc},
+    cbnez{: c3rs1, x0, %reloc(I_cb_imm8) : next_pc},
       resources: {next_pc,c3rs1, x0 : next_pc[0..]},
       disasm: "c.bnez", "%c3rs1, %(@+I_cb_imm8:08x)",
       semfunc: "&RV64::RiscVIBne";
@@ -962,7 +962,7 @@
       semfunc: "&RV64::RiscVIAdd";
     clui{: I_ci_imm6_12 : rd},
       resources: {next_pc : rd[0..]},
-      disasm: "c.lui", "%rd, 0x%(I_ci_imm6_12:x)",
+      disasm: "c.lui", "%rd, %(I_ci_imm6_12:x)",
       semfunc: "&RV64::RiscVILui";
     caddi{: rd, I_ci_imm6 : rd},
       resources: {next_pc, rd : rd[0..]},
@@ -982,15 +982,15 @@
       semfunc: "&RV64::RiscVIAdd";
     cslli{: rd, I_ci_uimm6 : rd},
       resources: {next_pc, rd : rd[0..]},
-      disasm: "c.slli", "%rd, %rd, 0x%(I_ci_uimm6:x)",
+      disasm: "c.slli", "%rd, %rd, %(I_ci_uimm6:x)",
       semfunc: "&RV64::RiscVISll";
     csrli{: c3rs1, I_ci_uimm6 : c3rs1},
       resources: {next_pc, c3rs1 : c3rs1[0..]},
-      disasm: "c.srli", "%c3rs1, %c3rs1, 0x%(I_ci_uimm6:x)",
+      disasm: "c.srli", "%c3rs1, %c3rs1, %(I_ci_uimm6:x)",
       semfunc: "&RV64::RiscVISrl";
     csrai{: c3rs1, I_ci_uimm6 : c3rs1},
       resources: {next_pc, c3rs1 : c3rs1[0..]},
-      disasm: "c.srai", "%c3rs1, %c3rs1, 0x%(I_ci_uimm6:x)",
+      disasm: "c.srai", "%c3rs1, %c3rs1, %(I_ci_uimm6:x)",
       semfunc: "&RV64::RiscVISra";
     candi{: c3rs1, I_ci_imm6 : c3rs1},
       resources: {next_pc, c3rs1 : c3rs1[0..]},
diff --git a/riscv/riscv64g_as_main.cc b/riscv/riscv64g_as_main.cc
new file mode 100644
index 0000000..3893308
--- /dev/null
+++ b/riscv/riscv64g_as_main.cc
@@ -0,0 +1,205 @@
+// 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 <cstdint>
+#include <fstream>
+#include <iostream>
+#include <optional>
+#include <string>
+#include <vector>
+
+#include "absl/flags/flag.h"
+#include "absl/flags/parse.h"
+#include "absl/log/check.h"
+#include "absl/log/log.h"
+#include "absl/status/status.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
+#include "elfio/elf_types.hpp"
+#include "elfio/elfio.hpp"
+#include "elfio/elfio_dump.hpp"
+#include "mpact/sim/generic/type_helpers.h"
+#include "mpact/sim/util/asm/opcode_assembler_interface.h"
+#include "mpact/sim/util/asm/resolver_interface.h"
+#include "mpact/sim/util/asm/simple_assembler.h"
+#include "re2/re2.h"
+#include "riscv/riscv64g_bin_encoder_interface.h"
+#include "riscv/riscv64g_encoder.h"
+
+using ::mpact::sim::riscv::isa64::RiscV64GBinEncoderInterface;
+using ::mpact::sim::riscv::isa64::Riscv64gSlotMatcher;
+using ::mpact::sim::util::assembler::SimpleAssembler;
+
+// This file implements the main function for the generated assembler for
+// RiscV64G.
+
+namespace {
+
+using ::mpact::sim::generic::operator*;  // NOLINT(misc-unused-using-decls)
+using ::mpact::sim::util::assembler::OpcodeAssemblerInterface;
+using ::mpact::sim::util::assembler::RelocationInfo;
+using ::mpact::sim::util::assembler::ResolverInterface;
+using AddSymbolCallback =
+    ::mpact::sim::util::assembler::OpcodeAssemblerInterface::AddSymbolCallback;
+
+// This class implements the byte oriented OpcodeAssemblerInterface, converting
+// from the bit interface provided by the SlotMatcher interface. Since there is
+// only one slot in the RiscV64G ISA, only one slot matcher is needed.
+class RiscV64GAssembler : public OpcodeAssemblerInterface {
+ public:
+  RiscV64GAssembler(Riscv64gSlotMatcher* matcher)
+      : label_re_("^(\\S+)\\s*:"), matcher_(matcher) {};
+  ~RiscV64GAssembler() override = default;
+  absl::Status Encode(uint64_t address, absl::string_view text,
+                      AddSymbolCallback add_symbol_callback,
+                      ResolverInterface* resolver, std::vector<uint8_t>& bytes,
+                      std::vector<RelocationInfo>& relocations) override {
+    // First check to see if there is a label, if so, add it to the symbol table
+    // with the current address.
+    std::string label;
+    if (RE2::Consume(&text, label_re_, &label)) {
+      auto status = add_symbol_callback(label, address, 0, STT_NOTYPE, 0, 0);
+      if (!status.ok()) return status;
+    }
+    auto res = matcher_->Encode(address, text, 0, resolver, relocations);
+    if (!res.status().ok()) return res.status();
+    auto [value, size] = res.value();
+    union {
+      uint64_t i;
+      uint8_t b[sizeof(uint64_t)];
+    } u;
+    u.i = value;
+    for (int i = 0; i < size / 8; ++i) {
+      bytes.push_back(u.b[i]);
+    }
+    return absl::OkStatus();
+  }
+
+ private:
+  RE2 label_re_;
+  Riscv64gSlotMatcher* matcher_;
+};
+
+}  // namespace
+
+// This flag dumps the info for the generated ELF file.
+ABSL_FLAG(bool, dump_elf, false, "Dump the ELF file");
+// Produce a relocatable file as opposed to an executable.
+ABSL_FLAG(bool, c, false, "Produce a relocatable file");
+// Specify the output file name.
+ABSL_FLAG(std::optional<std::string>, o, std::nullopt, "Output file name");
+
+// Supported RiscV ELF flags (TSO memory ordering and compact encodings).
+enum class RiscVElfFlags {
+  kNone = 0,
+  kRiscvTso = 0x0001,
+  kRiscvRvc = 0x0010,
+};
+
+int main(int argc, char* argv[]) {
+  auto arg_vec = absl::ParseCommandLine(argc, argv);
+
+  if (arg_vec.size() > 2) {
+    std::cout << "Too many arguments\n";
+    return 1;
+  }
+
+  std::istream* is;
+
+  if (arg_vec.size() == 1) {
+    is = &std::cin;
+  } else {
+    is = new std::fstream(arg_vec[1], std::fstream::in);
+  }
+
+  RiscV64GBinEncoderInterface bin_encoder_interface;
+  Riscv64gSlotMatcher matcher(&bin_encoder_interface);
+  RiscV64GAssembler riscv_64g_assembler(&matcher);
+  CHECK_OK(matcher.Initialize());
+  // Instantiate the assembler.
+  SimpleAssembler assembler("#", ELFCLASS64, &riscv_64g_assembler);
+  // Set up the abi and the machine type.
+  assembler.writer().set_os_abi(ELFOSABI_LINUX);
+  assembler.writer().set_machine(EM_RISCV);
+  // Set the appropriate ELF header flags.
+  assembler.writer().set_flags(*RiscVElfFlags::kRiscvTso |
+                               *RiscVElfFlags::kRiscvRvc);
+  // Perform the first pass of parsing the assembly code.
+  auto status = assembler.Parse(*is);
+  if (!status.ok()) {
+    std::cout << "Failed to parse assembly: " << status.message() << "\n";
+    return 1;
+  }
+  // Perform the second pass of parsing the assembly code. This pass will either
+  // generate an executable or a relocatable file.
+  std::string output_file_name;
+  if (absl::GetFlag(FLAGS_c)) {
+    status = assembler.CreateRelocatable();
+    if (arg_vec.size() == 1) {
+      output_file_name = "stdin.o";
+    } else {
+      std::string input_file_name = arg_vec[1];
+      auto dot_pos = input_file_name.find_last_of('.');
+      if (dot_pos == std::string::npos) {
+        output_file_name = absl::StrCat(input_file_name, ".o");
+      } else {
+        output_file_name =
+            absl::StrCat(input_file_name.substr(0, dot_pos), ".o");
+      }
+    }
+  } else {
+    status = assembler.CreateExecutable(0x1000, "main");
+    output_file_name = "a.out";
+  }
+  if (!status.ok()) {
+    std::cout << "Assembly failure: " << status.message() << "\n";
+    return 1;
+  }
+  // Write out the output file.
+  if (absl::GetFlag(FLAGS_o).has_value()) {
+    output_file_name = absl::GetFlag(FLAGS_o).value();
+  }
+  std::ofstream output_file(output_file_name);
+  if (!output_file.is_open()) {
+    std::cout << "Failed to open output file: " << output_file_name << "\n";
+    return 1;
+  }
+  status = assembler.Write(output_file);
+  if (!status.ok()) {
+    std::cout << "Failed to write output file: " << status.message() << "\n";
+    return 1;
+  }
+  output_file.close();
+  if (is != &std::cin) delete is;
+
+  // Dump the ELF info if requested.
+  if (absl::GetFlag(FLAGS_dump_elf)) {
+    ELFIO::elfio reader;
+    if (!reader.load(output_file_name)) {
+      std::cout << "Failed to load output file: " << output_file_name << "\n";
+      return 1;
+    }
+
+    ELFIO::dump::header(std::cout, reader);
+    ELFIO::dump::section_headers(std::cout, reader);
+    ELFIO::dump::segment_headers(std::cout, reader);
+    ELFIO::dump::symbol_tables(std::cout, reader);
+    ELFIO::dump::notes(std::cout, reader);
+    ELFIO::dump::modinfo(std::cout, reader);
+    ELFIO::dump::dynamic_tags(std::cout, reader);
+    ELFIO::dump::section_datas(std::cout, reader);
+    ELFIO::dump::segment_datas(std::cout, reader);
+  }
+  return 0;
+}
diff --git a/riscv/riscv64g_bin_encoder_interface.cc b/riscv/riscv64g_bin_encoder_interface.cc
new file mode 100644
index 0000000..7e02c18
--- /dev/null
+++ b/riscv/riscv64g_bin_encoder_interface.cc
@@ -0,0 +1,135 @@
+// 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/riscv64g_bin_encoder_interface.h"
+
+#include <cstdint>
+#include <tuple>
+#include <vector>
+
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
+#include "mpact/sim/generic/type_helpers.h"
+#include "mpact/sim/util/asm/resolver_interface.h"
+#include "riscv/riscv64g_bin_encoder.h"
+#include "riscv/riscv64g_encoder.h"
+#include "riscv/riscv64g_enums.h"
+#include "riscv/riscv_bin_setters.h"
+
+namespace mpact {
+namespace sim {
+namespace riscv {
+namespace isa64 {
+
+using ::mpact::sim::generic::operator*;  // NOLINT(misc-unused-using-decls)
+using ::mpact::sim::util::assembler::ResolverInterface;
+
+RiscV64GBinEncoderInterface::RiscV64GBinEncoderInterface() {
+  AddRiscvSourceOpBinSetters<SourceOpEnum, OpMap, encoding64::Encoder>(
+      source_op_map_);
+  AddRiscvDestOpBinSetters<DestOpEnum, OpMap, encoding64::Encoder>(
+      dest_op_map_);
+  AddRiscvSourceOpRelocationSetters<OpcodeEnum, SourceOpEnum, RelocationMap>(
+      relocation_source_op_map_);
+}
+
+absl::StatusOr<std::tuple<uint64_t, int>>
+RiscV64GBinEncoderInterface::GetOpcodeEncoding(SlotEnum slot, int entry,
+                                               OpcodeEnum opcode,
+                                               ResolverInterface *resolver) {
+  return encoding64::kOpcodeEncodings->at(opcode);
+}
+
+absl::StatusOr<uint64_t> RiscV64GBinEncoderInterface::GetSrcOpEncoding(
+    uint64_t address, absl::string_view text, SlotEnum slot, int entry,
+    OpcodeEnum opcode, SourceOpEnum source_op, int source_num,
+    ResolverInterface *resolver) {
+  auto iter = source_op_map_.find(*source_op);
+  if (iter == source_op_map_.end()) {
+    return absl::NotFoundError(absl::StrCat(
+        "Source operand not found for op enum value ", *source_op));
+  }
+  return iter->second(address, text, resolver);
+}
+
+absl::Status RiscV64GBinEncoderInterface::AppendSrcOpRelocation(
+    uint64_t address, absl::string_view text, SlotEnum slot, int entry,
+    OpcodeEnum opcode, SourceOpEnum source_op, int source_num,
+    ResolverInterface *resolver, std::vector<RelocationInfo> &relocations) {
+  auto iter = relocation_source_op_map_.find(std::tie(opcode, source_op));
+  if (iter == relocation_source_op_map_.end()) return absl::OkStatus();
+  return iter->second(address, text, resolver, relocations);
+}
+
+absl::StatusOr<uint64_t> RiscV64GBinEncoderInterface::GetDestOpEncoding(
+    uint64_t address, absl::string_view text, SlotEnum slot, int entry,
+    OpcodeEnum opcode, DestOpEnum dest_op, int dest_num,
+    ResolverInterface *resolver) {
+  auto iter = dest_op_map_.find(*dest_op);
+  if (iter == dest_op_map_.end()) {
+    return absl::NotFoundError(
+        absl::StrCat("Dest operand not found for op enum value ", *dest_op));
+  }
+  return iter->second(address, text, resolver);
+}
+
+absl::StatusOr<uint64_t> RiscV64GBinEncoderInterface::GetListDestOpEncoding(
+    uint64_t address, absl::string_view text, SlotEnum slot, int entry,
+    OpcodeEnum opcode, ListDestOpEnum dest_op, int dest_num,
+    ResolverInterface *resolver) {
+  auto iter = list_dest_op_map_.find(*dest_op);
+  if (iter == list_dest_op_map_.end()) {
+    return absl::NotFoundError(absl::StrCat(
+        "List dest operand not found for op enum value ", *dest_op));
+  }
+  return iter->second(address, text, resolver);
+}
+
+absl::Status RiscV64GBinEncoderInterface::AppendDestOpRelocation(
+    uint64_t address, absl::string_view text, SlotEnum slot, int entry,
+    OpcodeEnum opcode, DestOpEnum dest_op, int dest_num,
+    ResolverInterface *resolver, std::vector<RelocationInfo> &relocations) {
+  // There are no destination operands that require relocation.
+  return absl::OkStatus();
+}
+
+absl::StatusOr<uint64_t> RiscV64GBinEncoderInterface::GetListSrcOpEncoding(
+    uint64_t address, absl::string_view text, SlotEnum slot, int entry,
+    OpcodeEnum opcode, ListSourceOpEnum source_op, int source_num,
+    ResolverInterface *resolver) {
+  auto iter = list_source_op_map_.find(*source_op);
+  if (iter == list_source_op_map_.end()) {
+    return absl::NotFoundError(absl::StrCat(
+        "List source operand not found for op enum value ", *source_op));
+  }
+  return iter->second(address, text, resolver);
+}
+
+absl::StatusOr<uint64_t> RiscV64GBinEncoderInterface::GetPredOpEncoding(
+    uint64_t address, absl::string_view text, SlotEnum slot, int entry,
+    OpcodeEnum opcode, PredOpEnum pred_op, ResolverInterface *resolver) {
+  auto iter = pred_op_map_.find(*pred_op);
+  if (iter == pred_op_map_.end()) {
+    return absl::NotFoundError(absl::StrCat(
+        "Predicate operand not found for op enum value ", *pred_op));
+  }
+  return iter->second(address, text, resolver);
+}
+
+}  // namespace isa64
+}  // namespace riscv
+}  // namespace sim
+}  // namespace mpact
diff --git a/riscv/riscv64g_bin_encoder_interface.h b/riscv/riscv64g_bin_encoder_interface.h
new file mode 100644
index 0000000..f25fe1d
--- /dev/null
+++ b/riscv/riscv64g_bin_encoder_interface.h
@@ -0,0 +1,110 @@
+// 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_RISCV64G_BIN_ENCODER_INTERFACE_H_
+#define THIRD_PARTY_MPACT_RISCV_RISCV64G_BIN_ENCODER_INTERFACE_H_
+
+#include <cstdint>
+#include <functional>
+#include <tuple>
+#include <vector>
+
+#include "absl/container/flat_hash_map.h"
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "absl/strings/string_view.h"
+#include "mpact/sim/util/asm/opcode_assembler_interface.h"
+#include "mpact/sim/util/asm/resolver_interface.h"
+#include "riscv/riscv64g_encoder.h"
+#include "riscv/riscv64g_enums.h"
+
+// This file defines the class that implements the RiscV64GEncoderInterfaceBase.
+// This class is used to convert from a text representation to the binary
+// encoding of opcodes, source and destination operands.
+
+namespace mpact {
+namespace sim {
+namespace riscv {
+namespace isa64 {
+
+using ::mpact::sim::util::assembler::RelocationInfo;
+using ::mpact::sim::util::assembler::ResolverInterface;
+
+class RiscV64GBinEncoderInterface : public RiscV64GEncoderInterfaceBase {
+ public:
+  RiscV64GBinEncoderInterface();
+  RiscV64GBinEncoderInterface(const RiscV64GBinEncoderInterface &) = delete;
+  RiscV64GBinEncoderInterface &operator=(const RiscV64GBinEncoderInterface &) =
+      delete;
+  ~RiscV64GBinEncoderInterface() override = default;
+
+  absl::StatusOr<std::tuple<uint64_t, int>> GetOpcodeEncoding(
+      SlotEnum slot, int entry, OpcodeEnum opcode,
+      ResolverInterface *resolver) override;
+  absl::StatusOr<uint64_t> GetSrcOpEncoding(
+      uint64_t address, absl::string_view text, SlotEnum slot, int entry,
+      OpcodeEnum opcode, SourceOpEnum source_op, int source_num,
+      ResolverInterface *resolver) override;
+  absl::Status AppendSrcOpRelocation(
+      uint64_t address, absl::string_view text, SlotEnum slot, int entry,
+      OpcodeEnum opcode, SourceOpEnum source_op, int source_num,
+      ResolverInterface *resolver,
+      std::vector<RelocationInfo> &relocations) override;
+  absl::StatusOr<uint64_t> GetDestOpEncoding(
+      uint64_t address, absl::string_view text, SlotEnum slot, int entry,
+      OpcodeEnum opcode, DestOpEnum dest_op, int dest_num,
+      ResolverInterface *resolver) override;
+  absl::Status AppendDestOpRelocation(
+      uint64_t address, absl::string_view text, SlotEnum slot, int entry,
+      OpcodeEnum opcode, DestOpEnum dest_op, int dest_num,
+      ResolverInterface *resolver,
+      std::vector<RelocationInfo> &relocations) override;
+  absl::StatusOr<uint64_t> GetListSrcOpEncoding(
+      uint64_t address, absl::string_view text, SlotEnum slot, int entry,
+      OpcodeEnum opcode, ListSourceOpEnum source_op, int source_num,
+      ResolverInterface *resolver) override;
+  absl::StatusOr<uint64_t> GetListDestOpEncoding(
+      uint64_t address, absl::string_view text, SlotEnum slot, int entry,
+      OpcodeEnum opcode, ListDestOpEnum dest_op, int dest_num,
+      ResolverInterface *resolver) override;
+  absl::StatusOr<uint64_t> GetPredOpEncoding(
+      uint64_t address, absl::string_view text, SlotEnum slot, int entry,
+      OpcodeEnum opcode, PredOpEnum pred_op,
+      ResolverInterface *resolver) override;
+
+ private:
+  using OpMap = absl::flat_hash_map<
+      int, std::function<absl::StatusOr<uint64_t>(uint64_t, absl::string_view,
+                                                  ResolverInterface *)>>;
+
+  using RelocationMap =
+      absl::flat_hash_map<std::tuple<OpcodeEnum, SourceOpEnum>,
+                          std::function<absl::Status(
+                              uint64_t, absl::string_view, ResolverInterface *,
+                              std::vector<RelocationInfo> &)>>;
+
+  OpMap source_op_map_;
+  RelocationMap relocation_source_op_map_;
+  OpMap dest_op_map_;
+  OpMap list_dest_op_map_;
+  OpMap list_source_op_map_;
+  OpMap pred_op_map_;
+};
+
+}  // namespace isa64
+}  // namespace riscv
+}  // namespace sim
+}  // namespace mpact
+
+#endif  // THIRD_PARTY_MPACT_RISCV_RISCV64G_BIN_ENCODER_INTERFACE_H_
diff --git a/riscv/riscv_arm_semihost.cc b/riscv/riscv_arm_semihost.cc
index 5a79e1e..b3ba038 100644
--- a/riscv/riscv_arm_semihost.cc
+++ b/riscv/riscv_arm_semihost.cc
@@ -238,7 +238,9 @@
     LOG(ERROR) << "Program exit not caught in ARM semihosting - no callback";
     return absl::NotFoundError("Exit callback not valid in ARM semihosting");
   }
-  LOG(ERROR) << "Exceptions other than ApplicationExit are not implemented";
+  LOG(ERROR) << absl::StrCat(
+      "Exception ", absl::Hex(param_value), " other than ApplicationExit (",
+      absl::Hex(kAdpStoppedApplicationExit), ") are not implemented");
   return absl::UnimplementedError(
       "Exceptions other than ApplicationExit are not implemented");
 }
diff --git a/riscv/riscv_arm_semihost.h b/riscv/riscv_arm_semihost.h
index 82ee35a..19d89bd 100644
--- a/riscv/riscv_arm_semihost.h
+++ b/riscv/riscv_arm_semihost.h
@@ -15,6 +15,7 @@
 #ifndef MPACT_RISCV_RISCV_RISCV_ARM_SEMIHOST_H_
 #define MPACT_RISCV_RISCV_RISCV_ARM_SEMIHOST_H_
 
+#include <cstdint>
 #include <functional>
 
 #include "absl/container/flat_hash_map.h"
diff --git a/riscv/riscv_bin_setters.cc b/riscv/riscv_bin_setters.cc
new file mode 100644
index 0000000..6d6684d
--- /dev/null
+++ b/riscv/riscv_bin_setters.cc
@@ -0,0 +1,145 @@
+// 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_bin_setters.h"
+
+#include <cstdint>
+#include <string>
+#include <vector>
+
+#include "absl/base/no_destructor.h"
+#include "absl/status/status.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
+#include "mpact/sim/generic/type_helpers.h"
+#include "re2/re2.h"
+
+namespace mpact {
+namespace sim {
+namespace riscv {
+
+namespace internal {
+
+// Current set of relocation types supported by the assembler.
+enum class RelocType {
+  kNone = 0,
+  kBranch = 16,
+  kJal = 17,
+  kPcrelHi20 = 23,
+  kPcrelLo12I = 24,
+  kPcrelLo12S = 25,
+  kHi20 = 26,
+  kLo12I = 27,
+  kLo12S = 28,
+};
+
+using ::mpact::sim::generic::operator*;  // NOLINT(misc-unused-using-decls)
+
+// This regular expression matches the text for a symbol. The first capture
+// group is the relocation type (if present), the second capture group is the
+// symbol name.
+absl::NoDestructor<RE2> kSymRe(
+    "^\\s*(%[a-zA-Z0-9_]+)?\\s*\\(?([a-zA-Z_][^)]+)\\)?\\s*$");
+
+absl::Status RelocateAddiIImm12(uint64_t address, absl::string_view text,
+                                ResolverInterface *resolver,
+                                std::vector<RelocationInfo> &relocations) {
+  std::string relo;
+  std::string sym;
+  if (!RE2::FullMatch(text, *kSymRe, &relo, &sym)) return absl::OkStatus();
+  if (relo == "%lo") {
+    relocations.emplace_back(0, sym, *RelocType::kLo12I, 0, 0);
+    return absl::OkStatus();
+  }
+  if (relo == "%pcrel_lo") {
+    relocations.emplace_back(0, sym, *RelocType::kPcrelLo12I, 0, 0);
+    return absl::OkStatus();
+  }
+  if (!relo.empty()) {
+    return absl::InvalidArgumentError(
+        absl::StrCat("Invalid relocation: '", relo, "'"));
+  }
+  return absl::OkStatus();
+}
+
+absl::Status RelocateJJImm20(uint64_t address, absl::string_view text,
+                             ResolverInterface *resolver,
+                             std::vector<RelocationInfo> &relocations) {
+  std::string relo;
+  std::string sym;
+  if (!RE2::FullMatch(text, *kSymRe, &relo, &sym)) return absl::OkStatus();
+  relocations.emplace_back(0, sym, *RelocType::kJal, 0, 0);
+  return absl::OkStatus();
+}
+
+absl::Status RelocateJrJImm12(uint64_t address, absl::string_view text,
+                              ResolverInterface *resolver,
+                              std::vector<RelocationInfo> &relocations) {
+  std::string relo;
+  std::string sym;
+  if (!RE2::FullMatch(text, *kSymRe, &relo, &sym)) return absl::OkStatus();
+  if (relo == "%pcrel_lo") {
+    relocations.emplace_back(0, sym, *RelocType::kPcrelLo12I, 0, 0);
+  }
+  return absl::OkStatus();
+}
+
+absl::Status RelocateLuiUImm20(uint64_t address, absl::string_view text,
+                               ResolverInterface *resolver,
+                               std::vector<RelocationInfo> &relocations) {
+  std::string relo;
+  std::string sym;
+  if (!RE2::FullMatch(text, *kSymRe, &relo, &sym)) return absl::OkStatus();
+  relocations.emplace_back(0, sym, *RelocType::kHi20, 0, 0);
+  return absl::OkStatus();
+}
+
+absl::Status RelocateSdSImm12(uint64_t address, absl::string_view text,
+                              ResolverInterface *resolver,
+                              std::vector<RelocationInfo> &relocations) {
+  std::string relo;
+  std::string sym;
+  if (!RE2::FullMatch(text, *kSymRe, &relo, &sym)) return absl::OkStatus();
+  if (relo == "%lo") {
+    relocations.emplace_back(0, sym, *RelocType::kLo12S, 0, 0);
+    return absl::OkStatus();
+  }
+  if (relo == "%pcrel_lo") {
+    relocations.emplace_back(0, sym, *RelocType::kPcrelLo12S, 0, 0);
+    return absl::OkStatus();
+  }
+  if (!relo.empty()) {
+    return absl::InvalidArgumentError(
+        absl::StrCat("Invalid relocation: '", relo, "'"));
+  }
+  return absl::OkStatus();
+}
+
+absl::Status RelocateAuipcUImm20(uint64_t address, absl::string_view text,
+                                 ResolverInterface *resolver,
+                                 std::vector<RelocationInfo> &relocations) {
+  std::string relo;
+  std::string sym;
+  if (!RE2::FullMatch(text, *kSymRe, &relo, &sym)) return absl::OkStatus();
+  if (relo == "%pcrel_hi") {
+    relocations.emplace_back(0, sym, *RelocType::kPcrelHi20, 0, 0);
+  }
+  return absl::OkStatus();
+}
+
+}  // namespace internal
+
+}  // namespace riscv
+}  // namespace sim
+}  // namespace mpact
diff --git a/riscv/riscv_bin_setters.h b/riscv/riscv_bin_setters.h
new file mode 100644
index 0000000..4eb312c
--- /dev/null
+++ b/riscv/riscv_bin_setters.h
@@ -0,0 +1,1040 @@
+// 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_BIN_SETTERS_H_
+#define THIRD_PARTY_MPACT_RISCV_RISCV_BIN_SETTERS_H_
+
+#include <cstdint>
+#include <initializer_list>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "absl/container/flat_hash_map.h"
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "absl/strings/numbers.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
+#include "mpact/sim/util/asm/opcode_assembler_interface.h"
+#include "mpact/sim/util/asm/resolver_interface.h"
+#include "re2/re2.h"
+#include "riscv/riscv_getter_helpers.h"
+
+// This file contains various setters for the RiscV binary encoder that is used
+// by the assembler to map from operand text strings to integer values.
+
+namespace mpact {
+namespace sim {
+namespace riscv {
+
+using ::mpact::sim::util::assembler::RelocationInfo;
+using ::mpact::sim::util::assembler::ResolverInterface;
+
+// Initializer lists for maps for different subsets of registers, mapping from
+// the register name to the register number.
+constexpr std::initializer_list<const std::pair<absl::string_view, uint64_t>>
+    kRegisterList = {
+        {"x0", 0},   {"x1", 1},   {"x2", 2},   {"x3", 3},   {"x4", 4},
+        {"x5", 5},   {"x6", 6},   {"x7", 7},   {"x8", 8},   {"x9", 9},
+        {"x10", 10}, {"x11", 11}, {"x12", 12}, {"x13", 13}, {"x14", 14},
+        {"x15", 15}, {"x16", 16}, {"x17", 17}, {"x18", 18}, {"x19", 19},
+        {"x20", 20}, {"x21", 21}, {"x22", 22}, {"x23", 23}, {"x24", 24},
+        {"x25", 25}, {"x26", 26}, {"x27", 27}, {"x28", 28}, {"x29", 29},
+        {"x30", 30}, {"x31", 31}, {"zero", 0}, {"ra", 1},   {"sp", 2},
+        {"gp", 3},   {"tp", 4},   {"t0", 5},   {"t1", 6},   {"t2", 7},
+        {"s0", 8},   {"s1", 9},   {"a0", 10},  {"a1", 11},  {"a2", 12},
+        {"a3", 13},  {"a4", 14},  {"a5", 15},  {"a6", 16},  {"a7", 17},
+        {"s2", 18},  {"s3", 19},  {"s4", 20},  {"s5", 21},  {"s6", 22},
+        {"s7", 23},  {"s8", 24},  {"s9", 25},  {"s10", 26}, {"s11", 27},
+        {"t3", 28},  {"t4", 29},  {"t5", 30},  {"t6", 31}};
+
+constexpr std::initializer_list<const std::pair<absl::string_view, uint64_t>>
+    kCRegisterList = {
+        {"x8", 8},   {"x9", 9},   {"x10", 10}, {"x11", 11},
+        {"x12", 12}, {"x13", 13}, {"x14", 14}, {"x15", 15},
+        {"s0", 8},   {"s1", 9},   {"a0", 10},  {"a1", 11},
+        {"a2", 12},  {"a3", 13},  {"a4", 14},  {"a5", 15},
+};
+
+constexpr std::initializer_list<const std::pair<absl::string_view, uint64_t>>
+    kFRegisterList = {
+        {"f0", 0},   {"f1", 1},   {"f2", 2},    {"f3", 3},    {"f4", 4},
+        {"f5", 5},   {"f6", 6},   {"f7", 7},    {"f8", 8},    {"f9", 9},
+        {"f10", 10}, {"f11", 11}, {"f12", 12},  {"f13", 13},  {"f14", 14},
+        {"f15", 15}, {"f16", 16}, {"f17", 17},  {"f18", 18},  {"f19", 19},
+        {"f20", 20}, {"f21", 21}, {"f22", 22},  {"f23", 23},  {"f24", 24},
+        {"f25", 25}, {"f26", 26}, {"f27", 27},  {"f28", 28},  {"f29", 29},
+        {"f30", 30}, {"f31", 31}, {"ft0", 0},   {"ft1", 1},   {"ft2", 2},
+        {"ft3", 3},  {"ft4", 4},  {"ft5", 5},   {"ft6", 6},   {"ft7", 7},
+        {"fs0", 8},  {"fs1", 9},  {"fa0", 10},  {"fa1", 11},  {"fa2", 12},
+        {"fa3", 13}, {"fa4", 14}, {"fa5", 15},  {"fa6", 16},  {"fa7", 17},
+        {"fs2", 18}, {"fs3", 19}, {"fs4", 20},  {"fs5", 21},  {"fs6", 22},
+        {"fs7", 23}, {"fs8", 24}, {"fs9", 25},  {"fs10", 26}, {"fs11", 27},
+        {"ft8", 28}, {"ft9", 29}, {"ft10", 30}, {"ft11", 31}};
+
+constexpr std::initializer_list<const std::pair<absl::string_view, uint64_t>>
+    kDRegisterList = {
+        {"d0", 0},   {"d1", 1},   {"d2", 2},    {"d3", 3},    {"d4", 4},
+        {"d5", 5},   {"d6", 6},   {"d7", 7},    {"d8", 8},    {"d9", 9},
+        {"d10", 10}, {"d11", 11}, {"d12", 12},  {"d13", 13},  {"d14", 14},
+        {"d15", 15}, {"d16", 16}, {"d17", 17},  {"d18", 18},  {"d19", 19},
+        {"d20", 20}, {"d21", 21}, {"d22", 22},  {"d23", 23},  {"d24", 24},
+        {"d25", 25}, {"d26", 26}, {"d27", 27},  {"d28", 28},  {"d29", 29},
+        {"d30", 30}, {"d31", 31}, {"dt0", 0},   {"dt1", 1},   {"dt2", 2},
+        {"dt3", 3},  {"dt4", 4},  {"dt5", 5},   {"dt6", 6},   {"dt7", 7},
+        {"ds0", 8},  {"ds1", 9},  {"da0", 10},  {"da1", 11},  {"da2", 12},
+        {"da3", 13}, {"da4", 14}, {"da5", 15},  {"da6", 16},  {"da7", 17},
+        {"ds2", 18}, {"ds3", 19}, {"ds4", 20},  {"ds5", 21},  {"ds6", 22},
+        {"ds7", 23}, {"ds8", 24}, {"ds9", 25},  {"ds10", 26}, {"ds11", 27},
+        {"dt8", 28}, {"dt9", 29}, {"dt10", 30}, {"dt11", 31}};
+
+constexpr std::initializer_list<const std::pair<absl::string_view, uint64_t>>
+    kDCRegisterList = {
+        {"d8", 8},   {"d9", 9},   {"d10", 10}, {"d11", 11},
+        {"d12", 12}, {"d13", 13}, {"d14", 14}, {"d15", 15},
+        {"ds0", 8},  {"ds1", 9},  {"da0", 10}, {"da1", 11},
+        {"da2", 12}, {"da3", 13}, {"da4", 14}, {"da5", 15},
+};
+
+// This is the initializer list for the map from CSR register names to register
+// numbers.
+constexpr std::initializer_list<const std::pair<absl::string_view, uint64_t>>
+    kCsrRegisterList = {
+        {"fflags", 0x001},
+        {"frm", 0x002},
+        {"fcsr", 0x003},
+        {"cycle", 0xc00},
+        {"time", 0xc01},
+        {"instret", 0xc02},
+        {"hpmcounter3", 0xc03},
+        {"hpmcounter4", 0xc04},
+        {"hpmcounter5", 0xc05},
+        {"hpmcounter6", 0xc06},
+        {"hpmcounter7", 0xc07},
+        {"hpmcounter8", 0xc08},
+        {"hpmcounter9", 0xc09},
+        {"hpmcounter10", 0xc0a},
+        {"hpmcounter11", 0xc0b},
+        {"hpmcounter12", 0xc0c},
+        {"hpmcounter13", 0xc0d},
+        {"hpmcounter14", 0xc0e},
+        {"hpmcounter15", 0xc0f},
+        {"hpmcounter16", 0xc10},
+        {"hpmcounter17", 0xc11},
+        {"hpmcounter18", 0xc12},
+        {"hpmcounter19", 0xc13},
+        {"hpmcounter20", 0xc14},
+        {"hpmcounter21", 0xc15},
+        {"hpmcounter22", 0xc16},
+        {"hpmcounter23", 0xc17},
+        {"hpmcounter24", 0xc18},
+        {"hpmcounter25", 0xc19},
+        {"hpmcounter26", 0xc1a},
+        {"hpmcounter27", 0xc1b},
+        {"hpmcounter28", 0xc1c},
+        {"hpmcounter29", 0xc1d},
+        {"hpmcounter30", 0xc1e},
+        {"hpmcounter31", 0xc1f},
+        {"cycleh", 0xc80},
+        {"timeh", 0xc81},
+        {"instreth", 0xc82},
+        {"hpmcounter3h", 0xc83},
+        {"hpmcounter4h", 0xc84},
+        {"hpmcounter5h", 0xc85},
+        {"hpmcounter6h", 0xc86},
+        {"hpmcounter7h", 0xc87},
+        {"hpmcounter8h", 0xc88},
+        {"hpmcounter9h", 0xc89},
+        {"hpmcounter10h", 0xc8a},
+        {"hpmcounter11h", 0xc8b},
+        {"hpmcounter12h", 0xc8c},
+        {"hpmcounter13h", 0xc8d},
+        {"hpmcounter14h", 0xc8e},
+        {"hpmcounter15h", 0xc8f},
+        {"hpmcounter16h", 0xc90},
+        {"hpmcounter17h", 0xc91},
+        {"hpmcounter18h", 0xc92},
+        {"hpmcounter19h", 0xc93},
+        {"hpmcounter20h", 0xc94},
+        {"hpmcounter2h1", 0xc95},
+        {"hpmcounter22h", 0xc96},
+        {"hpmcounter23h", 0xc97},
+        {"hpmcounter24h", 0xc98},
+        {"hpmcounter25h", 0xc99},
+        {"hpmcounter26h", 0xc9a},
+        {"hpmcounter27h", 0xc9b},
+        {"hpmcounter28h", 0xc9c},
+        {"hpmcounter29h", 0xc9d},
+        {"hpmcounter30h", 0xc9e},
+        {"hpmcounter3h1", 0xc9f},
+        {"sstatus", 0x100},
+        {"sie", 0x104},
+        {"stvec", 0x105},
+        {"stcounteren", 0x106},
+        {"senvcfg", 0x10a},
+        {"scountinhibit", 0x120},
+        {"sscratch", 0x140},
+        {"sepc", 0x141},
+        {"scause", 0x142},
+        {"stval", 0x143},
+        {"sip", 0x144},
+        {"scountovf", 0xda0},
+        {"satp", 0x180},
+        {"scontext", 0x5a8},
+        {"sstateen0", 0x10c},
+        {"sstateen1", 0x10d},
+        {"sstateen2", 0x10e},
+        {"sstateen3", 0x10f},
+        {"hstatus", 0x600},
+        {"hedeleg", 0x602},
+        {"hideleg", 0x603},
+        {"hie", 0x604},
+        {"hcounteren", 0x606},
+        {"hgeie", 0x607},
+        {"hedelegh", 0x612},
+        {"htval", 0x643},
+        {"hip", 0x644},
+        {"hvip", 0x645},
+        {"htinst", 0x64a},
+        {"hgeip", 0xe12},
+        {"henvcfg", 0x60a},
+        {"henvcfgh", 0x61a},
+        {"hgatp", 0x680},
+        {"hcontext", 0x6a8},
+        {"htimedelta", 0x605},
+        {"htimedeltah", 0x615},
+        {"hstateen0", 0x60c},
+        {"hstateen1", 0x60d},
+        {"hstateen2", 0x60e},
+        {"hstateen3", 0x60f},
+        {"hstateen0h", 0x61c},
+        {"hstateen1h", 0x61d},
+        {"hstateen2h", 0x61e},
+        {"hstateen3h", 0x61f},
+        {"vsstatus", 0x200},
+        {"vsie", 0x204},
+        {"vstvec", 0x205},
+        {"vsscratch", 0x240},
+        {"vsepc", 0x241},
+        {"vscause", 0x242},
+        {"vstval", 0x243},
+        {"vsip", 0x244},
+        {"vsatp", 0x280},
+        {"mvendorid", 0xf11},
+        {"marchid", 0xf12},
+        {"mimpid", 0xf13},
+        {"mhartid", 0xf14},
+        {"mconfigptr", 0xf15},
+        {"mstatus", 0x300},
+        {"misa", 0x301},
+        {"medeleg", 0x302},
+        {"mideleg", 0x303},
+        {"mie", 0x304},
+        {"mcounteren", 0x306},
+        {"mstatush", 0x310},
+        {"medelegh", 0x312},
+        {"mscratch", 0x340},
+        {"mepc", 0x341},
+        {"mcause", 0x342},
+        {"mtval", 0x343},
+        {"mip", 0x344},
+        {"mtinst", 0x34a},
+        {"mtval2", 0x34b},
+        {"menvcfg", 0x30a},
+        {"menvcfgh", 0x31a},
+        {"mseccfg", 0x747},
+        {"mseccfgh", 0x757},
+        {"pmpcfg0", 0x3a0},
+        {"pmpcfg1", 0x3a1},
+        {"pmpcfg2", 0x3a2},
+        {"pmpcfg3", 0x3a3},
+        {"pmpcfg4", 0x3a4},
+        {"pmpcfg5", 0x3a5},
+        {"pmpcfg6", 0x3a6},
+        {"pmpcfg7", 0x3a7},
+        {"pmpcfg8", 0x3a8},
+        {"pmpcfg9", 0x3a9},
+        {"pmpcfg10", 0x3aa},
+        {"pmpcfg11", 0x3ab},
+        {"pmpcfg12", 0x3ac},
+        {"pmpcfg13", 0x3ad},
+        {"pmpcfg14", 0x3ae},
+        {"pmpcfg15", 0x3af},
+        {"pmpaddr0", 0x3b0},
+        {"pmpaddr1", 0x3b1},
+        {"pmpaddr2", 0x3b2},
+        {"pmpaddr3", 0x3b3},
+        {"pmpaddr4", 0x3b4},
+        {"pmpaddr5", 0x3b5},
+        {"pmpaddr6", 0x3b6},
+        {"pmpaddr7", 0x3b7},
+        {"pmpaddr8", 0x3b8},
+        {"pmpaddr9", 0x3b9},
+        {"pmpaddr10", 0x3ba},
+        {"pmpaddr11", 0x3bb},
+        {"pmpaddr12", 0x3bc},
+        {"pmpaddr13", 0x3bd},
+        {"pmpaddr14", 0x3be},
+        {"pmpaddr15", 0x3bf},
+        {"pmpaddr16", 0x3c0},
+        {"pmpaddr17", 0x3c1},
+        {"pmpaddr18", 0x3c2},
+        {"pmpaddr19", 0x3c3},
+        {"pmpaddr20", 0x3c4},
+        {"pmpaddr21", 0x3c5},
+        {"pmpaddr22", 0x3c6},
+        {"pmpaddr23", 0x3c7},
+        {"pmpaddr24", 0x3c8},
+        {"pmpaddr25", 0x3c9},
+        {"pmpaddr26", 0x3ca},
+        {"pmpaddr27", 0x3cb},
+        {"pmpaddr28", 0x3cc},
+        {"pmpaddr29", 0x3cd},
+        {"pmpaddr30", 0x3ce},
+        {"pmpaddr31", 0x3cf},
+        {"pmpaddr32", 0x3d0},
+        {"pmpaddr33", 0x3d1},
+        {"pmpaddr34", 0x3d2},
+        {"pmpaddr35", 0x3d3},
+        {"pmpaddr36", 0x3d4},
+        {"pmpaddr37", 0x3d5},
+        {"pmpaddr38", 0x3d6},
+        {"pmpaddr39", 0x3d7},
+        {"pmpaddr40", 0x3d8},
+        {"pmpaddr41", 0x3d9},
+        {"pmpaddr42", 0x3da},
+        {"pmpaddr43", 0x3db},
+        {"pmpaddr44", 0x3dc},
+        {"pmpaddr45", 0x3dd},
+        {"pmpaddr46", 0x3de},
+        {"pmpaddr47", 0x3df},
+        {"pmpaddr48", 0x3e0},
+        {"pmpaddr49", 0x3e1},
+        {"pmpaddr50", 0x3e2},
+        {"pmpaddr51", 0x3e3},
+        {"pmpaddr52", 0x3e4},
+        {"pmpaddr53", 0x3e5},
+        {"pmpaddr54", 0x3e6},
+        {"pmpaddr55", 0x3e7},
+        {"pmpaddr56", 0x3e8},
+        {"pmpaddr57", 0x3e9},
+        {"pmpaddr58", 0x3ea},
+        {"pmpaddr59", 0x3eb},
+        {"pmpaddr60", 0x3ec},
+        {"pmpaddr61", 0x3ed},
+        {"pmpaddr62", 0x3ee},
+        {"pmpaddr63", 0x3ef},
+        {"mstateen0", 0x30c},
+        {"mstateen1", 0x30d},
+        {"mstateen2", 0x30e},
+        {"mstateen3", 0x30f},
+        {"mstateen0h", 0x31c},
+        {"mstateen1h", 0x31d},
+        {"mstateen2h", 0x31e},
+        {"mstateen3h", 0x31f},
+        {"mnscratch", 0x740},
+        {"mnepc", 0x741},
+        {"mncause", 0x742},
+        {"mnstatus", 0x743},
+        {"mcycle", 0xb00},
+        {"minstret", 0xb02},
+        {"mhpmcounter3", 0xb03},
+        {"mhpmcounter4", 0xb04},
+        {"mhpmcounter5", 0xb05},
+        {"mhpmcounter6", 0xb06},
+        {"mhpmcounter7", 0xb07},
+        {"mhpmcounter8", 0xb08},
+        {"mhpmcounter9", 0xb09},
+        {"mhpmcounter10", 0xb0a},
+        {"mhpmcounter11", 0xb0b},
+        {"mhpmcounter12", 0xb0c},
+        {"mhpmcounter13", 0xb0d},
+        {"mhpmcounter14", 0xb0e},
+        {"mhpmcounter15", 0xb0f},
+        {"mhpmcounter16", 0xb10},
+        {"mhpmcounter17", 0xb11},
+        {"mhpmcounter18", 0xb12},
+        {"mhpmcounter19", 0xb13},
+        {"mhpmcounter20", 0xb14},
+        {"mhpmcounter21", 0xb15},
+        {"mhpmcounter22", 0xb16},
+        {"mhpmcounter23", 0xb17},
+        {"mhpmcounter24", 0xb18},
+        {"mhpmcounter25", 0xb19},
+        {"mhpmcounter26", 0xb1a},
+        {"mhpmcounter27", 0xb1b},
+        {"mhpmcounter28", 0xb1c},
+        {"mhpmcounter29", 0xb1d},
+        {"mhpmcounter30", 0xb1e},
+        {"mhpmcounter31", 0xb1f},
+        {"mcycleh", 0xb80},
+        {"minstreth", 0xb82},
+        {"mhpmcounter3h", 0xb83},
+        {"mhpmcounter4h", 0xb84},
+        {"mhpmcounter5h", 0xb85},
+        {"mhpmcounter6h", 0xb86},
+        {"mhpmcounter7h", 0xb87},
+        {"mhpmcounter8h", 0xb88},
+        {"mhpmcounter9h", 0xb89},
+        {"mhpmcounter10h", 0xb8a},
+        {"mhpmcounter11h", 0xb8b},
+        {"mhpmcounter12h", 0xb8c},
+        {"mhpmcounter13h", 0xb8d},
+        {"mhpmcounter14h", 0xb8e},
+        {"mhpmcounter15h", 0xb8f},
+        {"mhpmcounter16h", 0xb90},
+        {"mhpmcounter17h", 0xb91},
+        {"mhpmcounter18h", 0xb92},
+        {"mhpmcounter19h", 0xb93},
+        {"mhpmcounter20h", 0xb94},
+        {"mhpmcounter21h", 0xb95},
+        {"mhpmcounter22h", 0xb96},
+        {"mhpmcounter23h", 0xb97},
+        {"mhpmcounter24h", 0xb98},
+        {"mhpmcounter25h", 0xb99},
+        {"mhpmcounter26h", 0xb9a},
+        {"mhpmcounter27h", 0xb9b},
+        {"mhpmcounter28h", 0xb9c},
+        {"mhpmcounter29h", 0xb9d},
+        {"mhpmcounter30h", 0xb9e},
+        {"mhpmcounter31h", 0xb9f},
+        {"mcountinhibit", 0x320},
+        {"mhpmevent3", 0x323},
+        {"mhpmevent4", 0x324},
+        {"mhpmevent5", 0x325},
+        {"mhpmevent6", 0x326},
+        {"mhpmevent7", 0x327},
+        {"mhpmevent8", 0x328},
+        {"mhpmevent9", 0x329},
+        {"mhpmevent10", 0x32a},
+        {"mhpmevent11", 0x32b},
+        {"mhpmevent12", 0x32c},
+        {"mhpmevent13", 0x32d},
+        {"mhpmevent14", 0x32e},
+        {"mhpmevent15", 0x32f},
+        {"mhpmevent16", 0x330},
+        {"mhpmevent17", 0x331},
+        {"mhpmevent18", 0x332},
+        {"mhpmevent19", 0x333},
+        {"mhpmevent20", 0x334},
+        {"mhpmevent21", 0x335},
+        {"mhpmevent22", 0x336},
+        {"mhpmevent23", 0x337},
+        {"mhpmevent24", 0x338},
+        {"mhpmevent25", 0x339},
+        {"mhpmevent26", 0x33a},
+        {"mhpmevent27", 0x33b},
+        {"mhpmevent28", 0x33c},
+        {"mhpmevent29", 0x33d},
+        {"mhpmevent30", 0x33e},
+        {"mhpmevent31", 0x33f},
+        {"mhpmevent3h", 0x723},
+        {"mhpmevent4h", 0x724},
+        {"mhpmevent5h", 0x725},
+        {"mhpmevent6h", 0x726},
+        {"mhpmevent7h", 0x727},
+        {"mhpmevent8h", 0x728},
+        {"mhpmevent9h", 0x729},
+        {"mhpmevent10h", 0x72a},
+        {"mhpmevent11h", 0x72b},
+        {"mhpmevent12h", 0x72c},
+        {"mhpmevent13h", 0x72d},
+        {"mhpmevent14h", 0x72e},
+        {"mhpmevent15h", 0x72f},
+        {"mhpmevent16h", 0x730},
+        {"mhpmevent17h", 0x731},
+        {"mhpmevent18h", 0x732},
+        {"mhpmevent19h", 0x733},
+        {"mhpmevent20h", 0x734},
+        {"mhpmevent21h", 0x735},
+        {"mhpmevent22h", 0x736},
+        {"mhpmevent23h", 0x737},
+        {"mhpmevent24h", 0x738},
+        {"mhpmevent25h", 0x739},
+        {"mhpmevent26h", 0x73a},
+        {"mhpmevent27h", 0x73b},
+        {"mhpmevent28h", 0x73c},
+        {"mhpmevent29h", 0x73d},
+        {"mhpmevent30h", 0x73e},
+        {"mhpmevent31h", 0x73f},
+};
+
+// A helper function to convert a text string to an integer. The function takes
+// either numeric literals (hexadecimal or decimal), symbol names, or relocation
+// functions, e.g., %hi(<symbol name>).
+template <typename T>
+absl::StatusOr<T> SimpleTextToInt(absl::string_view op_text,
+                                  ResolverInterface *resolver) {
+  T value;
+  static RE2 hex_re("^\\s*0x([0-9a-fA-F]+)\\s*$");
+  static RE2 dec_re("^\\s*(-?[0-9]+)\\s*$");
+  static RE2 relo_re("^\\s*\\%[a-zA-Z0-9_]+\\s*\\(([a-zA-Z0-9_]+)\\s*\\)\\s*$");
+  static RE2 symbol_re("^\\s*([a-zA-Z0-9_]+)\\s*$");
+  std::string str;
+  std::string text(op_text);
+  // First see if the operand is a relocation function, and extract the text
+  // argument. A relocation function is on the form of %name(arg).
+  if (RE2::FullMatch(op_text, relo_re, &str)) {
+    text = str;
+  }
+  // Extract the hex immediate.
+  if (RE2::FullMatch(text, hex_re, &str)) {
+    if (absl::SimpleHexAtoi(str, &value)) return value;
+    return absl::InvalidArgumentError(
+        absl::StrCat("Invalid hexadecimal immediate: ", text));
+  }
+  // Extract the decimal immediate.
+  if (RE2::FullMatch(text, dec_re, &str)) {
+    if (absl::SimpleAtoi(str, &value)) return value;
+    return absl::InvalidArgumentError(
+        absl::StrCat("Invalid decimal immediate: ", text));
+  }
+  // Extract the symbol.
+  if (RE2::FullMatch(text, symbol_re, &str)) {
+    if (resolver != nullptr) {
+      auto res = resolver->Resolve(str);
+      if (!res.ok()) {
+        return res.status();
+      }
+      return static_cast<T>(res.value());
+    }
+  }
+  return absl::InvalidArgumentError(absl::StrCat("Invalid argument: ", text));
+}
+
+using ValueMap = absl::flat_hash_map<absl::string_view, uint64_t>;
+
+// This function adds the bin setters for the source operands to the given map.
+template <typename Enum, typename Map, typename Encoder>
+void AddRiscvSourceOpBinSetters(Map &map) {
+  Insert(map, *Enum::kAAq,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           static ValueMap map = {{"", 0}, {".aq", 1}};
+           auto iter = map.find(text);
+           if (iter == map.end()) {
+             return absl::InvalidArgumentError(
+                 absl::StrCat("Invalid source operand: ", text));
+           }
+           return Encoder::AType::InsertAq(iter->second, 0ULL);
+         });
+  Insert(map, *Enum::kARl,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           static ValueMap map = {{"", 0}, {".rl", 1}};
+           auto iter = map.find(text);
+           if (iter == map.end()) {
+             return absl::InvalidArgumentError(
+                 absl::StrCat("Invalid source operand: ", text));
+           }
+           return Encoder::AType::InsertRl(iter->second, 0ULL);
+         });
+  Insert(map, *Enum::kBImm12,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           auto res = SimpleTextToInt<uint32_t>(text, resolver);
+           if (!res.ok()) return res.status();
+           uint32_t delta = res.value() - address;
+           return Encoder::BType::InsertBImm(delta, 0ULL);
+         });
+  Insert(map, *Enum::kC3drs2,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           static ValueMap map(kDCRegisterList);
+           auto iter = map.find(text);
+           if (iter == map.end()) {
+             return absl::InvalidArgumentError(
+                 absl::StrCat("Invalid source operand: ", text));
+           }
+           return Encoder::CS::InsertCsRs2(iter->second, 0ULL);
+         });
+  Insert(map, *Enum::kC3rs1,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           static ValueMap map(kCRegisterList);
+           auto iter = map.find(text);
+           if (iter == map.end()) {
+             return absl::InvalidArgumentError(
+                 absl::StrCat("Invalid source operand: ", text));
+           }
+           return Encoder::CL::InsertClRs1(iter->second, 0ULL);
+         });
+  Insert(map, *Enum::kC3rs2,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           static ValueMap map(kCRegisterList);
+           auto iter = map.find(text);
+           if (iter == map.end()) {
+             return absl::InvalidArgumentError(
+                 absl::StrCat("Invalid source operand: ", text));
+           }
+           return Encoder::CS::InsertCsRs2(iter->second, 0ULL);
+         });
+  Insert(map, *Enum::kCSRUimm5,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           auto res = SimpleTextToInt<uint32_t>(text, resolver);
+           if (!res.ok()) return res.status();
+           return Encoder::IType::InsertIUimm5(res.value(), 0ULL);
+         });
+  Insert(map, *Enum::kCdrs2,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           static ValueMap map(kDRegisterList);
+           auto iter = map.find(text);
+           if (iter == map.end()) {
+             return absl::InvalidArgumentError(
+                 absl::StrCat("Invalid source operand: ", text));
+           }
+           return Encoder::CR::InsertRs2(iter->second, 0ULL);
+         });
+  Insert(map, *Enum::kCrs1,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           static ValueMap map(kRegisterList);
+           auto iter = map.find(text);
+           if (iter == map.end()) {
+             return absl::InvalidArgumentError(
+                 absl::StrCat("Invalid source operand: ", text));
+           }
+           return Encoder::CR::InsertRs1(iter->second, 0ULL);
+         });
+  Insert(map, *Enum::kCrs2,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           static ValueMap map(kRegisterList);
+           auto iter = map.find(text);
+           if (iter == map.end()) {
+             return absl::InvalidArgumentError(
+                 absl::StrCat("Invalid source operand: ", text));
+           }
+           return Encoder::CR::InsertRs2(iter->second, 0ULL);
+         });
+  Insert(map, *Enum::kCsr,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           static ValueMap map(kCsrRegisterList);
+           auto iter = map.find(text);
+           if (iter == map.end()) {
+             return absl::InvalidArgumentError(
+                 absl::StrCat("Invalid source operand: ", text));
+           }
+           return Encoder::IType::InsertUImm12(iter->second, 0ULL);
+         });
+  Insert(map, *Enum::kDrs1,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           static ValueMap map(kDRegisterList);
+           auto iter = map.find(text);
+           if (iter == map.end()) {
+             return absl::InvalidArgumentError(
+                 absl::StrCat("Invalid source operand: ", text));
+           }
+           return Encoder::RType::InsertRs1(iter->second, 0ULL);
+         });
+  Insert(map, *Enum::kDrs2,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           static ValueMap map(kDRegisterList);
+           auto iter = map.find(text);
+           if (iter == map.end()) {
+             return absl::InvalidArgumentError(
+                 absl::StrCat("Invalid source operand: ", text));
+           }
+           return Encoder::RType::InsertRs2(iter->second, 0ULL);
+         });
+  Insert(map, *Enum::kDrs3,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           static ValueMap map(kDRegisterList);
+           auto iter = map.find(text);
+           if (iter == map.end()) {
+             return absl::InvalidArgumentError(
+                 absl::StrCat("Invalid source operand: ", text));
+           }
+           return Encoder::R4Type::InsertRs3(iter->second, 0ULL);
+         });
+  Insert(map, *Enum::kFrs1,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           static ValueMap map(kFRegisterList);
+           auto iter = map.find(text);
+           if (iter == map.end()) {
+             return absl::InvalidArgumentError(
+                 absl::StrCat("Invalid source operand: ", text));
+           }
+           return Encoder::RType::InsertRs1(iter->second, 0ULL);
+         });
+  Insert(map, *Enum::kFrs2,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           static ValueMap map(kFRegisterList);
+           auto iter = map.find(text);
+           if (iter == map.end()) {
+             return absl::InvalidArgumentError(
+                 absl::StrCat("Invalid source operand: ", text));
+           }
+           return Encoder::RType::InsertRs1(iter->second, 0ULL);
+         });
+  Insert(map, *Enum::kFrs3,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           static ValueMap map(kFRegisterList);
+           auto iter = map.find(text);
+           if (iter == map.end()) {
+             return absl::InvalidArgumentError(
+                 absl::StrCat("Invalid source operand: ", text));
+           }
+           return Encoder::R4Type::InsertRs3(iter->second, 0ULL);
+         });
+  Insert(map, *Enum::kICbImm8,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           auto res = SimpleTextToInt<int32_t>(text, resolver);
+           if (!res.ok()) return res.status();
+           uint32_t delta = res.value() - address;
+           return Encoder::CB::InsertBimm(delta, 0ULL);
+         });
+  Insert(map, *Enum::kICiImm6,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           auto res = SimpleTextToInt<int32_t>(text, resolver);
+           if (!res.ok()) return res.status();
+           return Encoder::CI::InsertImm6(res.value(), 0ULL);
+         });
+  Insert(map, *Enum::kICiImm612,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           auto res = SimpleTextToInt<int32_t>(text, resolver);
+           if (!res.ok()) return res.status();
+           return Encoder::CI::InsertImm18(res.value(), 0ULL);
+         });
+  Insert(map, *Enum::kICiImm6x16,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           auto res = SimpleTextToInt<int32_t>(text, resolver);
+           if (!res.ok()) return res.status();
+           return Encoder::CI::InsertCiImm10(res.value(), 0ULL);
+         });
+  Insert(map, *Enum::kICiUimm6,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           auto res = SimpleTextToInt<int32_t>(text, resolver);
+           if (!res.ok()) return res.status();
+           return Encoder::CI::InsertUimm6(res.value(), 0ULL);
+         });
+  Insert(map, *Enum::kICiUimm6x4,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           auto res = SimpleTextToInt<int32_t>(text, resolver);
+           if (!res.ok()) return res.status();
+           return Encoder::CI::InsertCiImmW(res.value(), 0ULL);
+         });
+  Insert(map, *Enum::kICiUimm6x8,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           auto res = SimpleTextToInt<int32_t>(text, resolver);
+           if (!res.ok()) return res.status();
+           return Encoder::CI::InsertCiImmD(res.value(), 0ULL);
+         });
+  Insert(map, *Enum::kICiwUimm8x4,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           auto res = SimpleTextToInt<int32_t>(text, resolver);
+           if (!res.ok()) return res.status();
+           return Encoder::CIW::InsertCiwImm10(res.value(), 0ULL);
+         });
+  Insert(map, *Enum::kICjImm11,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           auto res = SimpleTextToInt<int32_t>(text, resolver);
+           if (!res.ok()) return res.status();
+           auto delta = res.value() - address;
+           return Encoder::CJ::InsertJimm(delta, 0ULL);
+         });
+  Insert(map, *Enum::kIClUimm5x4,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           auto res = SimpleTextToInt<uint32_t>(text, resolver);
+           if (!res.ok()) return res.status();
+           return Encoder::CL::InsertClImmW(res.value(), 0ULL);
+         });
+  Insert(map, *Enum::kIClUimm5x8,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           auto res = SimpleTextToInt<int32_t>(text, resolver);
+           if (!res.ok()) return res.status();
+           return Encoder::CL::InsertClImmD(res.value(), 0ULL);
+         });
+  Insert(map, *Enum::kICssUimm6x4,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           auto res = SimpleTextToInt<uint32_t>(text, resolver);
+           if (!res.ok()) return res.status();
+           return Encoder::CS::InsertCsImmW(res.value(), 0ULL);
+         });
+  Insert(map, *Enum::kICssUimm6x8,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           auto res = SimpleTextToInt<uint32_t>(text, resolver);
+           if (!res.ok()) return res.status();
+           return Encoder::CS::InsertCsImmD(res.value(), 0ULL);
+         });
+  Insert(map, *Enum::kIImm12,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           auto res = SimpleTextToInt<int32_t>(text, resolver);
+           if (!res.ok()) return res.status();
+           return Encoder::IType::InsertImm12(res.value(), 0ULL);
+         });
+  Insert(map, *Enum::kIUimm5,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           auto res = SimpleTextToInt<uint32_t>(text, resolver);
+           if (!res.ok()) return res.status();
+           return Encoder::RType::InsertRUimm5(res.value(), 0ULL);
+         });
+  Insert(map, *Enum::kIUimm6,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           auto res = SimpleTextToInt<uint32_t>(text, resolver);
+           if (!res.ok()) return res.status();
+           return Encoder::RSType::InsertRUimm6(res.value(), 0ULL);
+         });
+  Insert(map, *Enum::kJImm12,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           auto res = SimpleTextToInt<int32_t>(text, resolver);
+           if (!res.ok()) return res.status();
+           return Encoder::IType::InsertImm12(res.value(), 0ULL);
+         });
+  Insert(map, *Enum::kJImm20,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           auto res = SimpleTextToInt<int32_t>(text, resolver);
+           if (!res.ok()) return res.status();
+           uint32_t delta = res.value() - address;
+           auto value = Encoder::JType::InsertJImm(delta, 0ULL);
+           return value;
+         });
+  Insert(map, *Enum::kPred,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           auto res = SimpleTextToInt<uint32_t>(text, resolver);
+           if (!res.ok()) return res.status();
+           return Encoder::Fence::InsertPred(res.value(), 0ULL);
+         });
+  Insert(map, *Enum::kRd,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           static ValueMap map(kRegisterList);
+           auto iter = map.find(text);
+           if (iter == map.end()) {
+             return absl::InvalidArgumentError(
+                 absl::StrCat("Invalid source operand: ", text));
+           }
+           return Encoder::RType::InsertRd(iter->second, 0ULL);
+         });
+  Insert(map, *Enum::kRm,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           static ValueMap map(kRegisterList);
+           auto iter = map.find(text);
+           if (iter == map.end()) {
+             return absl::InvalidArgumentError(
+                 absl::StrCat("Invalid source operand: ", text));
+           }
+           return Encoder::R4Type::InsertRs3(iter->second, 0ULL);
+         });
+  Insert(map, *Enum::kRs1,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           static ValueMap map(kRegisterList);
+           auto iter = map.find(text);
+           if (iter == map.end()) {
+             return absl::InvalidArgumentError(
+                 absl::StrCat("Invalid source operand: ", text));
+           }
+           return Encoder::RType::InsertRs1(iter->second, 0ULL);
+         });
+  Insert(map, *Enum::kRs2,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           static ValueMap map(kRegisterList);
+           auto iter = map.find(text);
+           if (iter == map.end()) {
+             return absl::InvalidArgumentError(
+                 absl::StrCat("Invalid source operand: ", text));
+           }
+           return Encoder::RType::InsertRs2(iter->second, 0ULL);
+         });
+  Insert(map, *Enum::kSImm12,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           auto res = SimpleTextToInt<uint32_t>(text, resolver);
+           if (!res.ok()) return res.status();
+           return Encoder::SType::InsertSImm(res.value(), 0ULL);
+         });
+  Insert(map, *Enum::kSucc,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           auto res = SimpleTextToInt<uint32_t>(text, resolver);
+           if (!res.ok()) return res.status();
+           return Encoder::Fence::InsertSucc(res.value(), 0ULL);
+         });
+  Insert(map, *Enum::kUImm20,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           auto res = SimpleTextToInt<uint32_t>(text, resolver);
+           if (!res.ok()) return res.status();
+           return Encoder::UType::InsertUImm(res.value(), 0ULL);
+         });
+}
+
+// This function adds the destination operand setters for the RiscV ISA to the
+// given map.
+template <typename Enum, typename Map, typename Encoder>
+void AddRiscvDestOpBinSetters(Map &map) {
+  Insert(map, *Enum::kC3drd,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           static ValueMap map(kDCRegisterList);
+           auto iter = map.find(text);
+           if (iter == map.end()) {
+             return absl::InvalidArgumentError(
+                 absl::StrCat("Invalid destination operand: ", text));
+           }
+           return Encoder::CL::InsertClRd(iter->second, 0ULL);
+         });
+  Insert(map, *Enum::kC3rd,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           static ValueMap map(kCRegisterList);
+           auto iter = map.find(text);
+           if (iter == map.end()) {
+             return absl::InvalidArgumentError(
+                 absl::StrCat("Invalid destination operand: ", text));
+           }
+           return Encoder::CL::InsertClRd(iter->second, 0ULL);
+         });
+  Insert(map, *Enum::kC3rs1,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           static ValueMap map(kCRegisterList);
+           auto iter = map.find(text);
+           if (iter == map.end()) {
+             return absl::InvalidArgumentError(
+                 absl::StrCat("Invalid destination operand: ", text));
+           }
+           return Encoder::CS::InsertCsRs1(iter->second, 0ULL);
+         });
+  Insert(map, *Enum::kCsr,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           static ValueMap map(kCsrRegisterList);
+           auto iter = map.find(text);
+           if (iter == map.end()) {
+             return absl::InvalidArgumentError(
+                 absl::StrCat("Invalid source operand: ", text));
+           }
+           return Encoder::IType::InsertUImm12(iter->second, 0ULL);
+         });
+  Insert(map, *Enum::kDrd,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           static ValueMap map(kDRegisterList);
+           auto iter = map.find(text);
+           if (iter == map.end()) {
+             return absl::InvalidArgumentError(
+                 absl::StrCat("Invalid destination operand: ", text));
+           }
+           return Encoder::RType::InsertRd(iter->second, 0ULL);
+         });
+  Insert(map, *Enum::kFrd,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           static ValueMap map(kFRegisterList);
+           auto iter = map.find(text);
+           if (iter == map.end()) {
+             return absl::InvalidArgumentError(
+                 absl::StrCat("Invalid destination operand: ", text));
+           }
+           return Encoder::RType::InsertRd(iter->second, 0ULL);
+         });
+  Insert(map, *Enum::kRd,
+         [](uint64_t address, absl::string_view text,
+            ResolverInterface *resolver) -> absl::StatusOr<uint64_t> {
+           static ValueMap map(kRegisterList);
+           auto iter = map.find(text);
+           if (iter == map.end()) {
+             return absl::InvalidArgumentError(
+                 absl::StrCat("Invalid destination operand: ", text));
+           }
+           return Encoder::RType::InsertRd(iter->second, 0ULL);
+         });
+}
+
+// These functions add the appropriate relocation entry to the relocations
+// vector if the operand (in text) requires it.
+namespace internal {
+
+absl::Status RelocateAddiIImm12(uint64_t address, absl::string_view text,
+                                ResolverInterface *resolver,
+                                std::vector<RelocationInfo> &relocations);
+absl::Status RelocateJJImm20(uint64_t address, absl::string_view text,
+                             ResolverInterface *resolver,
+                             std::vector<RelocationInfo> &relocations);
+absl::Status RelocateJrJImm12(uint64_t address, absl::string_view text,
+                              ResolverInterface *resolver,
+                              std::vector<RelocationInfo> &relocations);
+absl::Status RelocateLuiUImm20(uint64_t address, absl::string_view text,
+                               ResolverInterface *resolver,
+                               std::vector<RelocationInfo> &relocations);
+absl::Status RelocateSdSImm12(uint64_t address, absl::string_view text,
+                              ResolverInterface *resolver,
+                              std::vector<RelocationInfo> &relocations);
+absl::Status RelocateAuipcUImm20(uint64_t address, absl::string_view text,
+                                 ResolverInterface *resolver,
+                                 std::vector<RelocationInfo> &relocations);
+
+}  // namespace internal
+
+// This function adds the source operand relocation setters for the RiscV ISA to
+// the given map. Notice that the key in the map is the tuple consisting of the
+// opcode and the source operand enum values.
+template <typename OpcodeEnum, typename SourceOpEnum, typename Map>
+void AddRiscvSourceOpRelocationSetters(Map &map) {
+  Insert(map, OpcodeEnum::kAddi, SourceOpEnum::kIImm12,
+         internal::RelocateAddiIImm12);
+  Insert(map, OpcodeEnum::kJal, SourceOpEnum::kJImm20,
+         internal::RelocateJJImm20);
+  Insert(map, OpcodeEnum::kJ, SourceOpEnum::kJImm20, internal::RelocateJJImm20);
+  Insert(map, OpcodeEnum::kJr, SourceOpEnum::kJImm12,
+         internal::RelocateJrJImm12);
+  Insert(map, OpcodeEnum::kLui, SourceOpEnum::kUImm20,
+         internal::RelocateLuiUImm20);
+  Insert(map, OpcodeEnum::kSd, SourceOpEnum::kSImm12,
+         internal::RelocateSdSImm12);
+  Insert(map, OpcodeEnum::kJalr, SourceOpEnum::kJImm12,
+         internal::RelocateJrJImm12);
+  Insert(map, OpcodeEnum::kAuipc, SourceOpEnum::kUImm20,
+         internal::RelocateAuipcUImm20);
+}
+
+}  // namespace riscv
+}  // namespace sim
+}  // namespace mpact
+
+#endif  // THIRD_PARTY_MPACT_RISCV_RISCV_BIN_SETTERS_H_
diff --git a/riscv/riscv_clint.h b/riscv/riscv_clint.h
index 468b8e6..9226c0b 100644
--- a/riscv/riscv_clint.h
+++ b/riscv/riscv_clint.h
@@ -18,6 +18,7 @@
 #include <cstdint>
 
 #include "mpact/sim/generic/counters.h"
+#include "mpact/sim/generic/counters_base.h"
 #include "mpact/sim/generic/data_buffer.h"
 #include "mpact/sim/generic/instruction.h"
 #include "mpact/sim/generic/ref_count.h"
@@ -117,4 +118,4 @@
 }  // namespace sim
 }  // namespace mpact
 
-#endif  // THIRD_PARTY_MPACT_RISCV_RISCV_CLINT_H_
\ No newline at end of file
+#endif  // THIRD_PARTY_MPACT_RISCV_RISCV_CLINT_H_
diff --git a/riscv/riscv_getter_helpers.h b/riscv/riscv_getter_helpers.h
index dc3f91d..408da03 100644
--- a/riscv/riscv_getter_helpers.h
+++ b/riscv/riscv_getter_helpers.h
@@ -63,6 +63,16 @@
   }
 }
 
+template <typename M, typename E1, typename E2, typename G>
+inline void Insert(M &map, E1 entry1, E2 entry2, G getter) {
+  auto key = std::tie(entry1, entry2);
+  if (!map.contains(key)) {
+    map.insert(std::make_pair(key, getter));
+  } else {
+    map.at(key) = getter;
+  }
+}
+
 // Generic helper functions to create register operands.
 template <typename RegType>
 inline DestinationOperandInterface *GetRegisterDestinationOp(RiscVState *state,
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_plic.cc b/riscv/riscv_plic.cc
new file mode 100644
index 0000000..10fb89c
--- /dev/null
+++ b/riscv/riscv_plic.cc
@@ -0,0 +1,587 @@
+// Copyright 2024 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/riscv_plic.h"
+
+#include <cstdint>
+#include <cstring>
+
+#include "absl/log/log.h"
+#include "absl/numeric/bits.h"
+#include "absl/status/status.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
+#include "re2/re2.h"
+
+namespace mpact {
+namespace sim {
+namespace riscv {
+
+RiscVPlic::RiscVPlic(int num_sources, int num_contexts)
+    : num_sources_(num_sources), num_contexts_(num_contexts) {
+  // Initialize the gateway info.
+  gateway_info_ = new GatewayInfo[num_sources_];
+  // Initialize the context interface.
+  context_if_ = new RiscVPlicIrqInterface *[num_contexts_];
+  context_irq_ = new bool[num_contexts_];
+  for (int i = 0; i < num_contexts_; ++i) {
+    context_if_[i] = nullptr;
+    context_irq_[i] = false;
+  }
+  // Initialize the interrupt priority.
+  interrupt_priority_ = new uint32_t[num_sources_];
+  std::memset(interrupt_priority_, 0, sizeof(uint32_t) * num_sources_);
+  // Initialize the interrupt pending bits.
+  interrupt_pending_ = new uint32_t[num_sources_ / 32 + 1];
+  std::memset(interrupt_pending_, 0,
+              sizeof(uint32_t) * (num_sources_ / 32 + 1));
+  // Initialize the interrupt enabled bits.
+  interrupt_enabled_ = new uint32_t *[num_contexts_];
+  for (int i = 0; i < num_contexts_; ++i) {
+    interrupt_enabled_[i] = new uint32_t[num_sources_ / 32 + 1];
+    std::memset(interrupt_enabled_[i], 0,
+                sizeof(uint32_t) * (num_sources_ / 32 + 1));
+  }
+  // Initialize the priority threshold.
+  priority_threshold_ = new uint32_t[num_contexts_];
+  std::memset(priority_threshold_, 0, sizeof(uint32_t) * num_contexts_);
+  // Initialize the interrupt claim/complete bits.
+  interrupt_claim_complete_ = new uint32_t[num_contexts_];
+  std::memset(interrupt_claim_complete_, 0, sizeof(uint32_t) * num_contexts_);
+}
+
+RiscVPlic::~RiscVPlic() {
+  // Clean up all the allocated memory.
+  delete[] gateway_info_;
+  gateway_info_ = nullptr;
+  delete[] context_if_;
+  context_if_ = nullptr;
+  delete[] context_irq_;
+  context_irq_ = nullptr;
+  delete[] interrupt_priority_;
+  interrupt_priority_ = nullptr;
+  delete[] interrupt_pending_;
+  interrupt_pending_ = nullptr;
+  for (int i = 0; i < num_contexts_; ++i) {
+    delete[] interrupt_enabled_[i];
+  }
+  delete[] interrupt_enabled_;
+  interrupt_enabled_ = nullptr;
+  delete[] priority_threshold_;
+  priority_threshold_ = nullptr;
+  delete[] interrupt_claim_complete_;
+  interrupt_claim_complete_ = nullptr;
+}
+
+absl::Status RiscVPlic::Configure(absl::string_view source_cfg,
+                                  absl::string_view context_cfg) {
+  // List of "<source>=<priority>;" items.
+  RE2 re_source("^(\\d+)\\s*=\\s*(\\d+)\\s*(;|$)");
+  int source;
+  int priority;
+  absl::string_view cfg = source_cfg;
+  while (RE2::Consume(&cfg, re_source, &source, &priority)) {
+    if (source >= num_sources_) {
+      return absl::InvalidArgumentError(
+          absl::StrCat("Invalid source number: ", source));
+    }
+    interrupt_priority_[source] = priority;
+  }
+  if (!cfg.empty()) {
+    return absl::InvalidArgumentError(
+        absl::StrCat("Invalid source configuration: ", cfg));
+  }
+  // List of "<context>=<source>,<threshold>,<enable>;" items.
+  RE2 re_context("^(\\d+)\\s*=\\s*(\\d+)\\s*,\\s*");
+  RE2 re_context_source("(\\d+)\\s*,\\s*(\\d)\\s*(,|;)\\s*");
+  int context;
+  int threshold;
+  int enable;
+  char terminator;
+  cfg = context_cfg;
+  while (RE2::Consume(&cfg, re_context, &context, &threshold)) {
+    if (context >= num_contexts_) {
+      return absl::InvalidArgumentError(
+          absl::StrCat("Invalid context number: ", context));
+    }
+    priority_threshold_[context] = threshold;
+    while (
+        RE2::Consume(&cfg, re_context_source, &source, &enable, &terminator)) {
+      if (source >= num_sources_) {
+        return absl::InvalidArgumentError(
+            absl::StrCat("Invalid source number: ", source));
+      }
+      source_to_context_.insert({source, context});
+      context_to_source_.insert({context, source});
+      int bit = source & 0x1f;
+      int word = source / 32;
+      uint32_t mask = ~(1 << bit);
+      uint32_t value = interrupt_enabled_[context][word];
+      value = (value & mask) | (enable << bit);
+      interrupt_enabled_[context][word] = value;
+      if (terminator == ';') break;
+    }
+  }
+  if (!cfg.empty()) {
+    return absl::InvalidArgumentError(
+        absl::StrCat("Invalid context configuration: ", cfg));
+  }
+  return absl::OkStatus();
+}
+
+void RiscVPlic::SetInterrupt(int source, bool value, bool is_level) {
+  // Make sure the source is in range.
+  if ((source < 0) || (source >= num_sources_)) {
+    LOG(WARNING) << "Invalid interrupt source: " << source;
+    return;
+  }
+  // No action for clearing a non-level based interrupt.
+  if (!value && !is_level) return;
+
+  auto &info = gateway_info_[source];
+  if (!info.ready || !value) {
+    if (is_level) info.pending = value;
+    return;
+  }
+
+  // If it is level based, latch the pending bit in the gateway.
+  if (is_level) info.pending = true;
+
+  // If the priority is 0, the interrupt is disabled.
+  if (interrupt_priority_[source] == 0) return;
+
+  // Accept the request, set ready to false to prevent any other requests
+  // until this has been processed.
+  info.ready = false;
+  // Set the plic pending bit.
+  SetPlicPendingInterrupt(source);
+}
+
+void RiscVPlic::SetPlicPendingInterrupt(int source) {
+  if ((source <= 0) || (source >= num_sources_)) {
+    LOG(ERROR) << "Invalid interrupt source: " << source;
+    return;
+  }
+  // Return if source is already pending.
+  if (IsPending(source)) return;
+  // Get interrupt priority.
+  auto priority = interrupt_priority_[source];
+  // Return if source has priority 0.
+  if (priority == 0) return;
+  // Set source to pending.
+  SetPending(source, true);
+  // Iterate over all contexts that have this source enabled.
+  auto [begin, end] = source_to_context_.equal_range(source);
+  for (auto it = begin; it != end; ++it) {
+    auto context = it->second;
+    // If the priority is less or equal to the threshold, do not trigger the
+    // interrupt.
+    if (priority <= priority_threshold_[context]) continue;
+    if (context_if_[context] == nullptr) continue;
+    // Trigger the interrupt.
+    context_if_[context]->SetIrq(true);
+    context_irq_[context] = true;
+  }
+}
+
+// Implementation of the memory load interface for reading memory mapped
+// registers.
+void RiscVPlic::Load(uint64_t address, DataBuffer *db, Instruction *inst,
+                     ReferenceCount *context) {
+  uint32_t offset = address & 0xff'ffff;
+  switch (db->size<uint8_t>()) {
+    case 1:
+      db->Set<uint8_t>(0, static_cast<uint8_t>(Read(offset)));
+      break;
+    case 2:
+      db->Set<uint16_t>(0, static_cast<uint16_t>(Read(offset)));
+      break;
+    case 4:
+      db->Set<uint32_t>(0, static_cast<uint32_t>(Read(offset)));
+      break;
+    case 8:
+      db->Set<uint32_t>(0, static_cast<uint32_t>(Read(offset)));
+      db->Set<uint32_t>(1, static_cast<uint32_t>(Read(offset + 4)));
+      break;
+    default:
+      ::memset(db->raw_ptr(), 0, sizeof(db->size<uint8_t>()));
+      break;
+  }
+  // Execute the instruction to process and write back the load data.
+  if (nullptr != inst) {
+    if (db->latency() > 0) {
+      inst->IncRef();
+      if (context != nullptr) context->IncRef();
+      inst->state()->function_delay_line()->Add(db->latency(),
+                                                [inst, context]() {
+                                                  inst->Execute(context);
+                                                  if (context != nullptr)
+                                                    context->DecRef();
+                                                  inst->DecRef();
+                                                });
+    } else {
+      inst->Execute(context);
+    }
+  }
+}
+
+// No support for vector loads.
+void RiscVPlic::Load(DataBuffer *address_db, DataBuffer *mask_db, int el_size,
+                     DataBuffer *db, Instruction *inst,
+                     ReferenceCount *context) {
+  LOG(FATAL) << "RiscVPlic does not support vector loads";
+}
+
+// Implementation of memory store interface to support writes to memory mapped
+// registers.
+void RiscVPlic::Store(uint64_t address, DataBuffer *db) {
+  uint32_t offset = address & 0xff'ffff;
+  switch (db->size<uint8_t>()) {
+    case 1:
+      return Write(offset, static_cast<uint32_t>(db->Get<uint8_t>(0)));
+    case 2:
+      return Write(offset, static_cast<uint32_t>(db->Get<uint16_t>(0)));
+    case 4:
+      return Write(offset, static_cast<uint32_t>(db->Get<uint32_t>(0)));
+    case 8:
+      return Write(offset, static_cast<uint32_t>(db->Get<uint32_t>(0)));
+      return Write(offset + 4, static_cast<uint32_t>(db->Get<uint32_t>(1)));
+    default:
+      return;
+  }
+}
+
+void RiscVPlic::SetContext(int context_no, RiscVPlicIrqInterface *context_if) {
+  context_if_[context_no] = context_if;
+}
+
+// No support for vector stores.
+void RiscVPlic::Store(DataBuffer *address, DataBuffer *mask, int el_size,
+                      DataBuffer *db) {
+  LOG(FATAL) << "RiscVPlic does not support vector stores";
+}
+
+uint32_t RiscVPlic::Read(uint32_t offset) {
+  uint32_t value = 0;
+  // Interrupt priority bit by source.
+  if (offset < 0x00'1000) {
+    value = interrupt_priority_[offset >> 2];
+    return value;
+  }
+
+  // Interrupt pending bits by source/
+  if (offset < 0x00'2000) {
+    offset -= 0x00'1000;
+    if (offset > (num_sources_ / 32)) {
+      LOG(WARNING) << "Invalid offset: " << offset;
+      return 0;
+    }
+    value = interrupt_pending_[offset];
+    return value;
+  }
+
+  // Interrupt enable bits for sources by context.
+  if (offset < 0x20'0000) {
+    offset -= 0x00'2000;
+    int context = offset >> 7;
+    // Get the context id.
+    if (context >= num_contexts_) {
+      LOG(ERROR) << "Invalid context number: " << context;
+      return 0;
+    }
+    // Get the word index in the source dimension.
+    int word = offset & 0x7f;
+    if (word * 32 >= num_sources_ + 32) {
+      LOG(ERROR) << "Invalid source number";
+      return 0;
+    }
+    value = interrupt_enabled_[context][word];
+    return value;
+  }
+
+  // Interrupt priority threshold and claim/complete.
+  // Get context id.
+  offset -= 0x20'0000;
+  int context = offset >> 12;
+  if (context >= num_contexts_) {
+    LOG(ERROR) << "Invalid context number: " << context;
+    return 0;
+  }
+  auto reg_id = offset & 0xfff;
+  switch (reg_id) {
+    case 0x0:
+      // Priority threshold.
+      value = priority_threshold_[context];
+      return value;
+    case 0x4:
+      // Claim/complete interrupt.
+      value = ClaimInterrupt(context);
+      return value;
+    default:
+      LOG(ERROR) << "Invalid offset: " << offset;
+      break;
+  }
+  return value;
+}
+
+void RiscVPlic::Write(uint32_t offset, uint32_t value) {
+  // Interrupt priority bit by source.
+  if (offset < 0x00'1000) {
+    int source = offset >> 3;
+    if (source >= num_sources_) {
+      LOG(ERROR) << "Invalid source number: " << source;
+      return;
+    }
+    uint32_t prev = interrupt_priority_[source];
+    interrupt_priority_[source] = value;
+    // If the priority is being changed from 0 to non-zero, see if there is a
+    // pending level based interrupt, and if so, set the plic pending bit.
+    if (prev == 0 && value != 0) {
+      auto &info = gateway_info_[source];
+      if (info.ready && info.pending) {
+        SetPlicPendingInterrupt(source);
+      }
+    }
+    return;
+  }
+
+  // Interrupt pending bits.
+  if (offset < 0x00'2000) {
+    offset -= 0x00'1000;
+    if (offset > (num_sources_ / 32)) {
+      LOG(WARNING) << "Invalid offset: " << offset;
+      return;
+    }
+    uint32_t prev = interrupt_pending_[offset];
+    interrupt_pending_[offset] = value;
+    // Determine which bits are being set.
+    uint32_t bits_set = (value ^ prev) & value;
+    // Trigger interrupts for any of the newly set pending bits.
+    while (bits_set != 0) {
+      int bit = absl::countr_zero(bits_set);
+      int source = offset * 32 + bit;
+      bits_set &= ~(1 << bit);
+      auto [begin, end] = source_to_context_.equal_range(source);
+      for (auto it = begin; it != end; ++it) {
+        auto context = it->second;
+        // If the context IRQ line is not already set, see if the priority is
+        // above the threshold, and if so, set the IRQ line.
+        if (!context_irq_[context]) {
+          // If the priority is less or equal to the threshold, do not trigger
+          // the interrupt.
+          if (interrupt_priority_[source] <= priority_threshold_[context])
+            continue;
+          if (context_if_[context] == nullptr) {
+            LOG(ERROR) << "No context interface for context " << context;
+            continue;
+          }
+          // Trigger the interrupt.
+          context_if_[context]->SetIrq(true);
+          context_irq_[context] = true;
+        }
+      }
+    }
+    return;
+  }
+
+  // Interrupt enable bits for sources by context.
+  if (offset < 0x20'0000) {
+    offset -= 0x00'2000;
+    // Convert from word address to index.
+    offset >>= 2;
+    // Get the word index in the source dimension.
+    int word = (offset >> 2) & 0x1f;
+    // Get the context id.
+    int context = offset >> 7;
+    uint32_t prev = interrupt_enabled_[context][word];
+    // Determine which bits are being set.
+    uint32_t bits_set = (value ^ prev) & value;
+    uint32_t bits_cleared = (value ^ prev) & ~value;
+    // Update the enable bits.
+    interrupt_enabled_[context][word] = value;
+    // Iterate over the set bits and add them to the source/context maps.
+    while (bits_set != 0) {
+      int bit = absl::countr_zero(bits_set);
+      int source = word * 32 + bit;
+      source_to_context_.insert({source, context});
+      context_to_source_.insert({context, source});
+      bits_set &= ~(1 << bit);
+      if (context_if_[context] == nullptr) {
+        LOG(ERROR) << "No context interface for context " << context;
+        continue;
+      }
+      // If there is no pending IRQ for this context, and the priority is
+      // above the threshold, set the IRQ.
+      if (!context_irq_[context]) {
+        if (interrupt_priority_[source] > priority_threshold_[context]) {
+          context_irq_[context] = true;
+          context_if_[context]->SetIrq(true);
+        }
+      }
+    }
+    // Iterate over the cleared bits and erase them from the source/context
+    // maps.
+    while (bits_cleared != 0) {
+      int bit = absl::countr_zero(bits_cleared);
+      int source = word * 32 + bit;
+      auto [s2c_begin, s2c_end] = source_to_context_.equal_range(source);
+      for (auto s2c_it = s2c_begin; s2c_it != s2c_end; ++s2c_it) {
+        if (s2c_it->second == context) {
+          source_to_context_.erase(s2c_it);
+          break;
+        }
+      }
+      auto [c2s_begin, c2s_end] = context_to_source_.equal_range(context);
+      for (auto c2s_it = c2s_begin; c2s_it != c2s_end; ++c2s_it) {
+        if (c2s_it->second == source) {
+          context_to_source_.erase(c2s_it);
+          break;
+        }
+      }
+      bits_cleared &= ~(1 << bit);
+    }
+    return;
+  }
+
+  offset -= 0x20'0000;
+  // Interrupt priority threshold and claim/complete.
+  // Get context id.
+  int context = offset >> 12;
+  if (context >= num_contexts_) {
+    LOG(ERROR) << "Invalid context number: " << context;
+    return;
+  }
+  auto reg_id = offset & 0xfff;
+  switch (reg_id) {
+    case 0x0: {
+      // Priority threshold.
+      uint32_t prev = priority_threshold_[context];
+      priority_threshold_[context] = value;
+      if (value < prev) {
+        // If the priority threshold is being lowered and there is no active
+        // interrupt, see if there is a pending interrupt for this context,
+        // and if so, set the IRQ.
+        if (!context_irq_[context]) {
+          if (context_if_[context] == nullptr) {
+            LOG(ERROR) << "No context interface for context " << context;
+            return;
+          }
+          auto [begin, end] = context_to_source_.equal_range(context);
+          for (auto it = begin; it != end; ++it) {
+            if (interrupt_priority_[it->second] > value) {
+              context_irq_[context] = true;
+              context_if_[context]->SetIrq(true);
+              break;
+            }
+          }
+        }
+      }
+      return;
+    }
+    case 0x4:
+      // Claim/complete interrupt.
+      CompleteInterrupt(context, value);
+      return;
+    default:
+      LOG(ERROR) << "Invalid offset: " << offset;
+      break;
+  }
+}
+
+uint32_t RiscVPlic::ClaimInterrupt(int context) {
+  if (context < 0 || context >= num_contexts_) return 0;
+  uint32_t id = 0;
+  int priority = 0;
+  // Find the id of the highest-priority pending interrupt for this context.
+  auto [begin, end] = context_to_source_.equal_range(context);
+  int count = 0;
+  int source = 0;
+  for (auto it = begin; it != end; ++it) {
+    source = it->second;
+    // If the source is not pending, skip it.
+    if (!IsPending(source)) continue;
+    // If the priority is less or equal to the current priority, skip it.
+    if (priority > interrupt_priority_[source]) continue;
+    // If the priority is the same, the lower id is chosen, so if the new
+    // source is greater than the current, go to the next.
+    if ((priority == interrupt_priority_[source]) && (id < source)) continue;
+    id = source;
+    priority = interrupt_priority_[source];
+    count++;
+  }
+  if (id != 0) {
+    SetPending(id, false);
+    interrupt_claim_complete_[context] = id;
+    count--;
+  }
+  if (count == 0) {
+    // If there are zero remaining pending interrupts, clear the IRQ line.
+    context_if_[context]->SetIrq(false);
+    context_irq_[context] = false;
+  }
+  return id;
+}
+
+void RiscVPlic::CompleteInterrupt(int context, uint32_t id) {
+  if (context < 0 || context >= num_contexts_) return;
+  // Check if id is in the set of enabled sources for context.
+  auto [begin, end] = context_to_source_.equal_range(context);
+  bool found = false;
+  for (auto it = begin; it != end; ++it) {
+    if (it->second == id) {
+      found = true;
+      break;
+    }
+  }
+  if (!found) return;
+  // The PLIC spec only requires that the id be valid for the set of
+  // interrupts enabled for the context, not that it matches the
+  // interrupt_claim_complete_ value.
+  auto source = interrupt_claim_complete_[context];
+  interrupt_claim_complete_[context] = 0;
+  auto &info = gateway_info_[source];
+  // Check to see if there's a pending level based interrupt w priority > 0.
+  if (info.pending && (interrupt_priority_[source] > 0)) {
+    // Set the plic pending bit but no need to set the ready bit as this will
+    // be forwarded to the plic core right away.
+    SetPlicPendingInterrupt(source);
+    return;
+  }
+  // Set the gateway ready bit to true.
+  info.ready = true;
+}
+
+void RiscVPlic::SetPending(int source, bool value) {
+  int word = source >> 5;
+  int bit = source & 0x1f;
+  if (value) {
+    interrupt_pending_[word] |= 1 << bit;
+  } else {
+    interrupt_pending_[word] &= ~(1 << bit);
+  }
+}
+
+bool RiscVPlic::IsPending(int source) {
+  int word = source >> 5;
+  int bit = source & 0x1f;
+  return (interrupt_pending_[word] & (1 << bit)) != 0;
+}
+
+RiscVPlicSourceInterface::RiscVPlicSourceInterface(RiscVPlic *plic, int source,
+                                                   bool is_level)
+    : plic_(plic), source_(source), is_level_(is_level) {}
+
+}  // namespace riscv
+}  // namespace sim
+}  // namespace mpact
diff --git a/riscv/riscv_plic.h b/riscv/riscv_plic.h
new file mode 100644
index 0000000..8d8745f
--- /dev/null
+++ b/riscv/riscv_plic.h
@@ -0,0 +1,173 @@
+// Copyright 2024 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_RISCV_PLIC_H_
+#define THIRD_PARTY_MPACT_RISCV_RISCV_PLIC_H_
+
+#include <cstdint>
+
+#include "absl/container/btree_map.h"
+#include "absl/status/status.h"
+#include "absl/strings/string_view.h"
+#include "mpact/sim/generic/data_buffer.h"
+#include "mpact/sim/generic/instruction.h"
+#include "mpact/sim/generic/ref_count.h"
+#include "mpact/sim/util/memory/memory_interface.h"
+
+// This file implements the RiscV PLIC (Platform Level Interrupt Controller).
+// It has a memory mapped register interface that is used to control the
+// interrupts to one or more contexts across one or more RiscV cores.
+//
+// The interrupt targets (contexts) are notified using the
+// RiscVPlicIrqInterface interface. Therefore each target much register its
+// own instance of this interface with the plic using the SetContext method.
+//
+// Interrupt sources communicate with the PLIC using the SetInterrupt method.
+//
+// The PLIC supports both level and edge triggered interrupts.
+
+namespace mpact {
+namespace sim {
+namespace riscv {
+
+using ::mpact::sim::generic::DataBuffer;
+using ::mpact::sim::generic::Instruction;
+using ::mpact::sim::generic::ReferenceCount;
+
+using ::mpact::sim::util::MemoryInterface;
+
+class RiscVPlicIrqInterface {
+ public:
+  virtual ~RiscVPlicIrqInterface() = default;
+  virtual void SetIrq(bool irq_value) = 0;
+};
+
+class RiscVPlic : public MemoryInterface {
+ public:
+  // The constructor takes the number of interrupt sources and the number of
+  // contexts that the PLIC can send interrupts to. A core usually has multiple
+  // contexts, one for each privilege level (machine, supervisor, etc.) that is
+  // capable of receiving and handling interrupts.
+  RiscVPlic(int num_sources, int num_contexts);
+  RiscVPlic() = delete;
+  RiscVPlic(const RiscVPlic &) = delete;
+  RiscVPlic &operator=(const RiscVPlic &) = delete;
+  ~RiscVPlic() override;
+
+  // Configure the PLIC state according to the source and context configuration
+  // strings. The source configuration string is a semicolon separated list of
+  // <source>=<priority> items, where <source> is the interrupt source number
+  // and <priority> is an non-negative integer priority value. Higher values
+  // have higher priorities. A value of zero disables the source. Any source
+  // that is not configured is disabled by default. However, MMR writes can
+  // change the configured values.
+  //
+  // The context configuration string is a semicolon separated list of
+  // <context>=<threshold>,(<source>,<enable>)+ items, where <context> is the
+  // context number, <source> is the source number, <threshold> is a non
+  // negative integer that is the priority threshold for that context, and
+  // <enable> is a 0 or 1 value that indicates whether the interrupt source is
+  // enabled for that context. Multiple lines for the same context with
+  // different sources is allowed. Any context not configured is assumed to
+  // disable all sources with a zero priority threshold.
+  absl::Status Configure(absl::string_view source_cfg,
+                         absl::string_view context_cfg);
+  // Interrupt request from the given interrupt source.
+  void SetInterrupt(int source, bool value, bool is_level);
+
+  // MemoryInterface overrides.
+  // Non-vector load method.
+  void Load(uint64_t address, DataBuffer *db, Instruction *inst,
+            ReferenceCount *context) override;
+  // Vector load method - this is stubbed out.
+  void Load(DataBuffer *address_db, DataBuffer *mask_db, int el_size,
+            DataBuffer *db, Instruction *inst,
+            ReferenceCount *context) override;
+  // Non-vector store method.
+  void Store(uint64_t address, DataBuffer *db) override;
+  // Vector store method - this is stubbed out.
+  void Store(DataBuffer *address, DataBuffer *mask, int el_size,
+             DataBuffer *db) override;
+
+  void SetContext(int context_no, RiscVPlicIrqInterface *context_if);
+
+ private:
+  struct GatewayInfo {
+    // The gateway is able to accept the interrupt and send it to the plic core.
+    bool ready;
+    // Pending bit in gateway. Only gets set if the interrupt is level based.
+    bool pending;
+    GatewayInfo() : ready(true), pending(false) {}
+  };
+
+  // MMR read/write methods.
+  uint32_t Read(uint32_t offset);
+  void Write(uint32_t offset, uint32_t value);
+  // Interrupt claim.
+  uint32_t ClaimInterrupt(int context);
+  // Signal interrupt completion for the given context and interrupt id.
+  void CompleteInterrupt(int context, uint32_t id);
+  // Set plic core pending interrupt bit and trigger interrupt to context as
+  // needed.
+  void SetPlicPendingInterrupt(int source);
+  // Handling pending bits.
+  void SetPending(int source, bool value);
+  bool IsPending(int source);
+
+  int num_sources_;
+  int num_contexts_;
+  // Interface to call to write the IRQ line for a context.
+  RiscVPlicIrqInterface **context_if_;
+  // Last value written to the IRQ line for a context.
+  bool *context_irq_ = nullptr;
+  // Source gateway info.
+  GatewayInfo *gateway_info_ = nullptr;
+  // Interrupt priorities by source.
+  uint32_t *interrupt_priority_ = nullptr;
+  // Pending interrupts by source - 32 bits per word.
+  uint32_t *interrupt_pending_ = nullptr;
+  // Enable bits per context per source - 32 bits per word.
+  // Array is organized as interrupt_enabled_[context][source / 32].
+  uint32_t **interrupt_enabled_ = nullptr;
+  // Priority threshold by context.
+  uint32_t *priority_threshold_ = nullptr;
+  // Interrupt claim/complete register by context.
+  uint32_t *interrupt_claim_complete_ = nullptr;
+  // Map from source to context that has the source enabled. This must be
+  // updated whenever an an enable bit is changed for a context.
+  absl::btree_multimap<int, int> source_to_context_;
+  // Map from context to source for which the context has the source enabled.
+  absl::btree_multimap<int, int> context_to_source_;
+};
+
+class RiscVPlicSourceInterface : public RiscVPlicIrqInterface {
+ public:
+  RiscVPlicSourceInterface(RiscVPlic *plic, int source, bool is_level);
+  RiscVPlicSourceInterface() = delete;
+  ~RiscVPlicSourceInterface() override = default;
+  void SetIrq(bool irq_value) override {
+    if (plic_ != nullptr) plic_->SetInterrupt(source_, irq_value, is_level_);
+  };
+
+ private:
+  RiscVPlic *plic_ = nullptr;
+  int source_ = 0;
+  bool is_level_ = false;
+};
+
+}  // namespace riscv
+}  // namespace sim
+}  // namespace mpact
+
+#endif  // THIRD_PARTY_MPACT_RISCV_RISCV_PLIC_H_
diff --git a/riscv/riscv_state.h b/riscv/riscv_state.h
index 1342a5c..a09c8d6 100644
--- a/riscv/riscv_state.h
+++ b/riscv/riscv_state.h
@@ -435,17 +435,16 @@
 
 // Specialization for RiscV vector registers.
 template <>
-inline std::pair<RVVectorRegister *, bool> RiscVState::GetRegister<RVVectorRegister>(
-    absl::string_view name) {
+inline std::pair<RVVectorRegister *, bool>
+RiscVState::GetRegister<RVVectorRegister>(absl::string_view name) {
   int vector_byte_width = vector_register_width();
   if (vector_byte_width == 0) return std::make_pair(nullptr, false);
   auto ptr = registers()->find(std::string(name));
   if (ptr != registers()->end())
-    return std::make_pair(static_cast<RVVectorRegister *>(ptr->second),
-                          false);
+    return std::make_pair(static_cast<RVVectorRegister *>(ptr->second), false);
   // Create a new register and return a pointer to the object.
-  return std::make_pair(
-      AddRegister<RVVectorRegister>(name, vector_byte_width), true);
+  return std::make_pair(AddRegister<RVVectorRegister>(name, vector_byte_width),
+                        true);
 }
 
 }  // namespace riscv
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_vector_permute_instructions.cc b/riscv/riscv_vector_permute_instructions.cc
index 9192c88..bf993d7 100644
--- a/riscv/riscv_vector_permute_instructions.cc
+++ b/riscv/riscv_vector_permute_instructions.cc
@@ -15,8 +15,11 @@
 #include "riscv/riscv_vector_permute_instructions.h"
 
 #include <algorithm>
+#include <cstdint>
 
 #include "absl/log/log.h"
+#include "absl/strings/str_cat.h"
+#include "absl/types/span.h"
 #include "mpact/sim/generic/data_buffer.h"
 #include "mpact/sim/generic/instruction.h"
 #include "riscv/riscv_register.h"
diff --git a/riscv/riscv_vector_unary_instructions.cc b/riscv/riscv_vector_unary_instructions.cc
index c6fc407..318ad24 100644
--- a/riscv/riscv_vector_unary_instructions.cc
+++ b/riscv/riscv_vector_unary_instructions.cc
@@ -15,10 +15,7 @@
 #include "riscv/riscv_vector_unary_instructions.h"
 
 #include <cstdint>
-#include <cstring>
 #include <functional>
-#include <optional>
-#include <type_traits>
 
 #include "absl/log/log.h"
 #include "absl/strings/str_cat.h"
@@ -77,8 +74,7 @@
   if (rv_vector->vstart()) return;
   if (rv_vector->vector_length() == 0) return;
   int sew = rv_vector->selected_element_width();
-  auto *dest_db = inst->Destination(0)->AllocateDataBuffer();
-  std::memset(dest_db->raw_ptr(), 0, dest_db->size<uint8_t>());
+  auto *dest_db = inst->Destination(0)->CopyDataBuffer();
   switch (sew) {
     case 1:
       dest_db->Set<int8_t>(0, generic::GetInstructionSource<int8_t>(inst, 0));
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 82910b6..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 = [
@@ -727,6 +744,21 @@
 )
 
 cc_test(
+    name = "riscv_plic_test",
+    size = "small",
+    srcs = [
+        "riscv_plic_test.cc",
+    ],
+    deps = [
+        "//riscv:riscv_plic",
+        "@com_google_absl//absl/log:check",
+        "@com_google_googletest//:gtest_main",
+        "@com_google_mpact-sim//mpact/sim/generic:core",
+        "@com_googlesource_code_re2//:re2",
+    ],
+)
+
+cc_test(
     name = "librenode_mpact_riscv32.so_test",
     size = "small",
     srcs = ["librenode_mpact_riscv32_so_test.cc"],
@@ -876,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_fp_test_base.h b/riscv/test/riscv_fp_test_base.h
index fc653da..f427a52 100644
--- a/riscv/test/riscv_fp_test_base.h
+++ b/riscv/test/riscv_fp_test_base.h
@@ -332,24 +332,26 @@
 
   // Creates source and destination scalar register operands for the registers
   // named in the two vectors and append them to the given instruction.
+  template <typename T>
   void AppendRegisterOperands(Instruction *inst,
                               const std::vector<std::string> &sources,
                               const std::vector<std::string> &destinations) {
     for (auto &reg_name : sources) {
-      auto *reg = state_->GetRegister<RV32Register>(reg_name).first;
+      auto *reg = state_->GetRegister<T>(reg_name).first;
       inst->AppendSource(reg->CreateSourceOperand());
     }
     for (auto &reg_name : destinations) {
-      auto *reg = state_->GetRegister<RV32Register>(reg_name).first;
+      auto *reg = state_->GetRegister<T>(reg_name).first;
       inst->AppendDestination(reg->CreateDestinationOperand(0));
     }
   }
 
   // Creates source and destination scalar register operands for the registers
   // named in the two vectors and append them to the default instruction.
+  template <typename T>
   void AppendRegisterOperands(const std::vector<std::string> &sources,
                               const std::vector<std::string> &destinations) {
-    AppendRegisterOperands(instruction_, sources, destinations);
+    AppendRegisterOperands<T>(instruction_, sources, destinations);
   }
 
   // named register and sets it to the corresponding value.
@@ -437,7 +439,8 @@
     const std::string kRdName = absl::StrCat(reg_prefixes[1], 5);
     // This is used for the rounding mode operand.
     const std::string kRmName = absl::StrCat("x", 10);
-    AppendRegisterOperands({kR1Name, kRmName}, {kRdName});
+    AppendRegisterOperands<RVFpRegister>({kR1Name}, {kRdName});
+    AppendRegisterOperands<RV32Register>({kRmName}, {});
     FillArrayWithRandomFPValues<LHS>(lhs_span);
     using LhsInt = typename FPTypeInfo<LHS>::IntType;
     *reinterpret_cast<LhsInt *>(&lhs_span[0]) = FPTypeInfo<LHS>::kQNaN;
@@ -488,7 +491,8 @@
     const std::string kRdName = absl::StrCat(reg_prefixes[1], 5);
     // This is used for the rounding mode operand.
     const std::string kRmName = absl::StrCat("x", 10);
-    AppendRegisterOperands({kR1Name, kRmName}, {kRdName});
+    AppendRegisterOperands<RVFpRegister>({kR1Name, kRmName}, {kRdName});
+    AppendRegisterOperands<RV32Register>({kRmName}, {});
     auto *flag_op = rv_fp_->fflags()->CreateSetDestinationOperand(0, "fflags");
     instruction_->AppendDestination(flag_op);
     FillArrayWithRandomFPValues<LHS>(lhs_span);
@@ -553,7 +557,8 @@
     const std::string kRdName = absl::StrCat(reg_prefixes[2], 5);
     // This is used for the rounding mode operand.
     const std::string kRmName = absl::StrCat("x", 10);
-    AppendRegisterOperands({kR1Name, kR2Name, kRmName}, {kRdName});
+    AppendRegisterOperands<RVFpRegister>({kR1Name, kR2Name}, {kRdName});
+    AppendRegisterOperands<RV32Register>({kRmName}, {});
     auto *flag_op = rv_fp_->fflags()->CreateSetDestinationOperand(0, "fflags");
     instruction_->AppendDestination(flag_op);
     FillArrayWithRandomFPValues<LHS>(lhs_span);
@@ -615,7 +620,8 @@
     const std::string kRdName = absl::StrCat(reg_prefixes[2], 5);
     // This is used for the rounding mode operand.
     const std::string kRmName = absl::StrCat("x", 10);
-    AppendRegisterOperands({kR1Name, kR2Name, kRmName}, {kRdName});
+    AppendRegisterOperands<RVFpRegister>({kR1Name, kR2Name}, {kRdName});
+    AppendRegisterOperands<RV32Register>({kRmName}, {});
     auto *flag_op = rv_fp_->fflags()->CreateSetDestinationOperand(0, "fflags");
     instruction_->AppendDestination(flag_op);
     FillArrayWithRandomFPValues<LHS>(lhs_span);
@@ -684,7 +690,9 @@
     const std::string kRdName = absl::StrCat(reg_prefixes[3], 5);
     // This is used for the rounding mode operand.
     const std::string kRmName = absl::StrCat("x", 10);
-    AppendRegisterOperands({kR1Name, kR2Name, kR3Name, kRmName}, {kRdName});
+    AppendRegisterOperands<RVFpRegister>({kR1Name, kR2Name, kR3Name},
+                                         {kRdName});
+    AppendRegisterOperands<RV32Register>({kRmName}, {});
     FillArrayWithRandomFPValues<LHS>(lhs_span);
     FillArrayWithRandomFPValues<MHS>(mhs_span);
     FillArrayWithRandomFPValues<RHS>(rhs_span);
@@ -745,7 +753,9 @@
     const std::string kRdName = absl::StrCat(reg_prefixes[3], 5);
     // This is used for the rounding mode operand.
     const std::string kRmName = absl::StrCat("x", 10);
-    AppendRegisterOperands({kR1Name, kR2Name, kR3Name, kRmName}, {kRdName});
+    AppendRegisterOperands<RVFpRegister>({kR1Name, kR2Name, kR3Name},
+                                         {kRdName});
+    AppendRegisterOperands<RV32Register>({kRmName}, {});
     auto *flag_op = rv_fp_->fflags()->CreateSetDestinationOperand(0, "fflags");
     instruction_->AppendDestination(flag_op);
     FillArrayWithRandomFPValues<LHS>(lhs_span);
diff --git a/riscv/test/riscv_plic_test.cc b/riscv/test/riscv_plic_test.cc
new file mode 100644
index 0000000..62c150c
--- /dev/null
+++ b/riscv/test/riscv_plic_test.cc
@@ -0,0 +1,315 @@
+
+// Copyright 2024 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/riscv_plic.h"
+
+#include <cstdint>
+
+#include "absl/log/check.h"
+#include "googlemock/include/gmock/gmock.h"
+#include "mpact/sim/generic/data_buffer.h"
+
+// This file contains unit tests for the RiscV PLIC model.
+
+namespace {
+
+using ::mpact::sim::generic::DataBuffer;
+using ::mpact::sim::generic::DataBufferFactory;
+using ::mpact::sim::riscv::RiscVPlic;
+using ::mpact::sim::riscv::RiscVPlicIrqInterface;
+
+constexpr int kNumSources = 32;
+constexpr int kNumContexts = 3;
+
+// Mock interrupt target for a context.
+class MockRiscVInterruptTarget : public RiscVPlicIrqInterface {
+ public:
+  void SetIrq(bool irq_value) override { irq_value_ = irq_value; }
+
+  bool irq_value() const { return irq_value_; }
+  void set_irq_value(bool value) { irq_value_ = value; }
+
+ private:
+  bool irq_value_ = false;
+};
+
+// Test fixture.
+class RiscVPlicTest : public ::testing::Test {
+ protected:
+  RiscVPlicTest() {
+    plic_ = new RiscVPlic(kNumSources, kNumContexts);
+    for (int i = 0; i < kNumContexts; ++i) {
+      target_[i] = new MockRiscVInterruptTarget();
+      plic_->SetContext(i, target_[i]);
+    }
+    db_ = db_factory_.Allocate<uint32_t>(1);
+    db_->set_latency(0);
+  }
+
+  ~RiscVPlicTest() override {
+    delete plic_;
+    for (int i = 0; i < kNumContexts; ++i) {
+      delete target_[i];
+    }
+    db_->DecRef();
+  }
+
+  // Convenience methods to read/write the PLIC state using its memory
+  // interface.
+  bool GetEnable(int source, int context) {
+    uint32_t word = 0x2000 + (context * 0x80) + (source >> 5);
+    int bit = source & 0x1f;
+    plic()->Load(word, db_, nullptr, nullptr);
+    return (db_->Get<uint32_t>(0) & (1 << bit)) != 0;
+  }
+
+  void SetEnable(int source, int context, bool value) {
+    uint32_t word = 0x2000 + (context * 0x80) + (source >> 5);
+    int bit = source & 0x1f;
+    plic()->Load(word, db_, nullptr, nullptr);
+    auto span = db_->Get<uint32_t>();
+    uint32_t mask = ~(1 << bit);
+    uint32_t u_val = static_cast<uint32_t>(value);
+    span[0] = (span[0] & mask) | (u_val << bit);
+    plic()->Store(word, db_);
+  }
+
+  bool GetPending(int source) {
+    uint32_t word = 0x1000 + ((source >> 5) << 2);
+    int bit = source & 0x1f;
+    plic()->Load(word, db_, nullptr, nullptr);
+    return (db_->Get<uint32_t>(0) & (1 << bit)) != 0;
+  }
+
+  void SetPending(int source, bool value) {
+    int word = 0x1000 + ((source >> 5) << 2);
+    int bit = source & 0x1f;
+    plic()->Load(word, db_, nullptr, nullptr);
+    auto span = db_->Get<uint32_t>();
+    uint32_t mask = ~(1 << bit);
+    span[0] = (span[0] & mask) | (static_cast<uint32_t>(value) << bit);
+    plic()->Store(word, db_);
+  }
+
+  uint32_t GetPriority(int source) {
+    plic()->Load(source << 2, db_, nullptr, nullptr);
+    return db_->Get<uint32_t>(0);
+  }
+
+  void SetPriority(int source, uint32_t priority) {
+    db_->Set<uint32_t>(0, priority);
+    plic()->Store(source << 2, db_);
+  }
+
+  int GetPriorityThreshold(int context) {
+    plic()->Load(0x20'0000 + context * 0x1000, db_, nullptr, nullptr);
+    return db_->Get<uint32_t>(0);
+  }
+
+  void SetPriorityThreshold(int context, uint32_t threshold) {
+    db_->Set<uint32_t>(0, threshold);
+    plic()->Store(0x20'0000 + context * 0x1000, db_);
+  }
+
+  uint32_t GetInterruptClaim(int context) {
+    plic()->Load(0x20'0000 + context * 0x1000 + 4, db_, nullptr, nullptr);
+    return db_->Get<uint32_t>(0);
+  }
+
+  void SetInterruptClaim(int context, uint32_t claim) {
+    db_->Set<uint32_t>(0, claim);
+    plic()->Store(0x20'0000 + context * 0x1000 + 4, db_);
+  }
+
+  void SetDefaultConfig() {
+    // Set priorities to increasing values for all sources.
+    // Thresholds are set 8, 16 and 24 for the three contexts respectively.
+    // Source 30 is enabled for all contexts.
+    auto status = plic()->Configure(
+        "0=0;1=1;2=2;3=3;4=4;5=5;6=6;7=7;8=8;9=9;10=10;11=11;"
+        "12=12;13=13;14=14;15=15;16=16;17=17;18=18;19=19;20=20;"
+        "21=21;22=22;23=23;24=24;25=25;26=26;27=27;28=28;29=29;"
+        "30=30;31=31;",
+        "0=8,1,1,2,1,3,1,4,1,5,1,6,1,7,1,8,1,9,1,10,1,30,1;"
+        "1=16,11,1,12,1,13,1,14,1,15,1,16,1,17,1,18,1,19,1,20,1,30,1;"
+        "2=24,19,1,20,1,21,1,22,1,23,1,24,1,25,1,26,1,27,1,28,1,29,1,30,1,31,"
+        "1;");
+    CHECK_OK(status);
+  }
+
+  // Accessors.
+  RiscVPlic *plic() { return plic_; }
+  DataBufferFactory &db_factory() { return db_factory_; }
+  MockRiscVInterruptTarget *target(int i) { return target_[i]; }
+
+ private:
+  DataBuffer *db_;
+  DataBufferFactory db_factory_;
+  RiscVPlic *plic_ = nullptr;
+  MockRiscVInterruptTarget *target_[kNumContexts];
+};
+
+// Test that the initial state of the PLIC is as expected. Nothing enabled,
+// priorities at zero, priority thresholds at zero, no pending interrupts,
+// no claimed interrupts, no IRQ lines set.
+TEST_F(RiscVPlicTest, InitialState) {
+  for (int s = 0; s < kNumSources; ++s) {
+    for (int c = 0; c < kNumContexts; ++c) {
+      EXPECT_FALSE(GetEnable(s, c));
+      EXPECT_FALSE(GetPending(s));
+      EXPECT_EQ(GetPriority(s), 0) << "Source " << s;
+      EXPECT_EQ(GetPriorityThreshold(c), 0);
+      EXPECT_EQ(GetInterruptClaim(c), 0);
+      EXPECT_FALSE(target(c)->irq_value());
+    }
+  }
+}
+
+// Verify that the PLIC is configured as expected with the default configuration
+// strings.
+TEST_F(RiscVPlicTest, DefaultConfiguration) {
+  SetDefaultConfig();
+  for (int s = 0; s < kNumSources; ++s) {
+    for (int c = 0; c < kNumContexts; ++c) {
+      switch (c) {
+        case 0:
+          EXPECT_EQ(GetEnable(s, c), ((s >= 1) && (s <= 10)) || (s == 30))
+              << "Source " << s << " context " << c;
+          break;
+        case 1:
+          EXPECT_EQ(GetEnable(s, c), ((s >= 11) && (s <= 20)) || (s == 30))
+              << "Source " << s << " context " << c;
+          break;
+        case 2:
+          EXPECT_EQ(GetEnable(s, c), (s >= 19) && (s <= 31))
+              << "Source " << s << " context " << c;
+          break;
+      }
+      EXPECT_FALSE(GetPending(s));
+      EXPECT_EQ(GetPriority(s), s) << "Source " << s;
+      EXPECT_EQ(GetPriorityThreshold(c), (c + 1) * 8) << "Context " << c;
+      EXPECT_EQ(GetInterruptClaim(c), 0) << "Context " << c;
+      EXPECT_FALSE(target(c)->irq_value()) << "Context " << c;
+    }
+  }
+}
+
+// Test operation of level triggered interrupts.
+TEST_F(RiscVPlicTest, LevelTriggeredInterrupt) {
+  SetDefaultConfig();
+  // Set irq for source 10 level sensitive.
+  plic()->SetInterrupt(10, /*value=*/true, /*is_level=*/true);
+  // Expect pending to be set.
+  EXPECT_TRUE(GetPending(10));
+  // Clear the irq.
+  plic()->SetInterrupt(10, /*value=*/false, /*is_level=*/true);
+  // Expect pending to still be set.
+  EXPECT_TRUE(GetPending(10));
+  // Expect irq to be set for context 0;
+  EXPECT_TRUE(target(0)->irq_value());
+  // Clear the irq.
+  target(0)->set_irq_value(false);
+  // Now raise the irq again.
+  plic()->SetInterrupt(10, /*value=*/true, /*is_level=*/true);
+  // Expect pending to be set.
+  EXPECT_TRUE(GetPending(10));
+  // Claim the interrupt.
+  uint32_t id = GetInterruptClaim(0);
+  EXPECT_EQ(id, 10);
+  // A second claim should return 0.
+  EXPECT_EQ(GetInterruptClaim(0), 0);
+  // Interrupt is no longer pending.
+  EXPECT_FALSE(GetPending(10));
+  // Complete the interrupt.
+  SetInterruptClaim(0, id);
+  // Expect pending to be set, as the level is still high.
+  EXPECT_TRUE(GetPending(10));
+  // Lower the irq.
+  plic()->SetInterrupt(10, /*value=*/false, /*is_level=*/true);
+  // Expect pending to still be set.
+  EXPECT_TRUE(GetPending(10));
+}
+
+// Test operation of edge triggered interrupts.
+TEST_F(RiscVPlicTest, EdgeTriggeredInterrupt) {
+  SetDefaultConfig();
+  // Set irq for source 10 level sensitive.
+  plic()->SetInterrupt(10, /*value=*/true, /*is_level=*/false);
+  // Expect pending to be set.
+  EXPECT_TRUE(GetPending(10));
+  // Expect irq to be set for context 0;
+  EXPECT_TRUE(target(0)->irq_value());
+  // Claim the interrupt.
+  uint32_t id = GetInterruptClaim(0);
+  EXPECT_EQ(id, 10);
+  // A second claim should return 0.
+  EXPECT_EQ(GetInterruptClaim(0), 0);
+  // Interrupt is no longer pending.
+  EXPECT_FALSE(GetPending(10));
+  // Complete the interrupt.
+  SetInterruptClaim(0, id);
+  // Expect pending to be cleared.
+  EXPECT_FALSE(GetPending(10));
+}
+
+TEST_F(RiscVPlicTest, PriorityThreshold) {
+  SetDefaultConfig();
+  // Signal interrupts for sources 5-10.
+  for (int i = 5; i <= 10; ++i) {
+    plic()->SetInterrupt(i, /*value=*/true, /*is_level=*/false);
+    // Only sources 9 and 10 should trigger interrupts.
+    EXPECT_EQ(target(0)->irq_value(), i > 8);
+  }
+  // Now claim the interrupts, all should be claimed in order of priority,'
+  // even those below the threshold. The IRQ line should remain high until
+  // there are no more pending interrupts.
+  for (int i = 10; i >= 5; --i) {
+    EXPECT_TRUE(target(0)->irq_value());
+    EXPECT_TRUE(GetPending(i));
+    uint32_t id = GetInterruptClaim(0);
+    EXPECT_EQ(id, i);
+    SetInterruptClaim(0, id);
+    EXPECT_FALSE(GetPending(i));
+    // IRQ remains high until the last interrupt is completed.
+    EXPECT_EQ(target(0)->irq_value(), i != 5);
+  }
+}
+
+TEST_F(RiscVPlicTest, MultipleTargets) {
+  SetDefaultConfig();
+  // Signal interrupt for source 30.
+  plic()->SetInterrupt(30, /*value=*/true, /*is_level=*/false);
+  EXPECT_TRUE(GetPending(30));
+  // Expect irq to be set for all contexts.
+  for (int c = 0; c < kNumContexts; ++c) {
+    EXPECT_TRUE(target(c)->irq_value());
+  }
+  // Try claiming the interrupt for each context, only the first will succeed.
+  for (int c = 0; c < kNumContexts; ++c) {
+    uint32_t id = GetInterruptClaim(c);
+    // Expect pending to be cleared.
+    EXPECT_FALSE(GetPending(30));
+    if (c == 0) {
+      EXPECT_EQ(id, 30);
+    } else {
+      EXPECT_EQ(id, 0);
+    }
+    // The target should be cleared.
+    EXPECT_EQ(target(c)->irq_value(), 0);
+  }
+  // Complete the interrupt for context 0.
+  SetInterruptClaim(0, 30);
+}
+}  // namespace
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..7ddd73d
--- /dev/null
+++ b/riscv/test/riscv_vector_basic_bit_manipulation_test.cc
@@ -0,0 +1,463 @@
+// 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 bitsize = sizeof(T) * 8;
+        T shift_mask = bitsize - 1;
+        uint8_t shiftl_amount = vs1 & shift_mask;
+        uint8_t shiftr_amount = (bitsize - shiftl_amount) & shift_mask;
+        return (vs2 << shiftl_amount) | (vs2 >> shiftr_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 bitsize = sizeof(T) * 8;
+        T shift_mask = bitsize - 1;
+        uint8_t shiftl_amount = rs1 & shift_mask;
+        uint8_t shiftr_amount = (bitsize - shiftl_amount) & shift_mask;
+        return (vs2 << shiftl_amount) | (vs2 >> shiftr_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 bitsize = sizeof(T) * 8;
+        T shift_mask = bitsize - 1;
+        uint8_t shiftr_amount = vs1 & shift_mask;
+        uint8_t shiftl_amount = (bitsize - shiftr_amount) & shift_mask;
+        return (vs2 << shiftl_amount) | (vs2 >> shiftr_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 bitsize = sizeof(T) * 8;
+        T shift_mask = bitsize - 1;
+        uint8_t shiftr_amount = rs1 & shift_mask;
+        uint8_t shiftl_amount = (bitsize - shiftr_amount) & shift_mask;
+        return (vs2 << shiftl_amount) | (vs2 >> shiftr_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 bitsize = sizeof(T) * 8;
+        T shift_mask = bitsize - 1;
+        uint8_t shiftr_amount = imm & shift_mask;
+        uint8_t shiftl_amount = (bitsize - shiftr_amount) & shift_mask;
+        return (vs2 << shiftl_amount) | (vs2 >> shiftr_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/riscv_vector_fp_compare_instructions_test.cc b/riscv/test/riscv_vector_fp_compare_instructions_test.cc
index 87648ef..7ef13c2 100644
--- a/riscv/test/riscv_vector_fp_compare_instructions_test.cc
+++ b/riscv/test/riscv_vector_fp_compare_instructions_test.cc
@@ -228,7 +228,7 @@
     Vs2 vs2_value[vs2_size * 8];
     auto vs2_span = Span<Vs2>(vs2_value);
     AppendVectorRegisterOperands({kVs2}, {});
-    AppendRegisterOperands({kFs1Name}, {});
+    AppendRegisterOperands<Fs1>({kFs1Name}, {});
     AppendVectorRegisterOperands({kVmask}, {kVd});
     // Initialize input values.
     FillArrayWithRandomValues<Vs2>(vs2_span);
diff --git a/riscv/test/riscv_vector_fp_instructions_test.cc b/riscv/test/riscv_vector_fp_instructions_test.cc
index 0aa2d50..c23519c 100644
--- a/riscv/test/riscv_vector_fp_instructions_test.cc
+++ b/riscv/test/riscv_vector_fp_instructions_test.cc
@@ -330,7 +330,7 @@
     auto vs2_span = Span<Vs2>(vs2_value);
     auto vd_span = Span<Vd>(vd_value);
     AppendVectorRegisterOperands({kVs2}, {kVd});
-    AppendRegisterOperands({kFs1Name}, {});
+    AppendRegisterOperands<RVFpRegister>({kFs1Name}, {});
     AppendVectorRegisterOperands({kVd, kVmask}, {kVd});
     SetVectorRegisterValues<uint8_t>(
         {{kVmaskName, Span<const uint8_t>(kA5Mask)}});
diff --git a/riscv/test/riscv_vector_fp_test_utilities.h b/riscv/test/riscv_vector_fp_test_utilities.h
index 96c8120..9ab9d3a 100644
--- a/riscv/test/riscv_vector_fp_test_utilities.h
+++ b/riscv/test/riscv_vector_fp_test_utilities.h
@@ -628,7 +628,7 @@
     Vs2 vs2_value[vs2_size * 8];
     auto vs2_span = Span<Vs2>(vs2_value);
     AppendVectorRegisterOperands({kVs2}, {kVd});
-    AppendRegisterOperands({kFs1Name}, {});
+    AppendRegisterOperands<Fs1>({kFs1Name}, {});
     auto *flag_op = rv_fp_->fflags()->CreateSetDestinationOperand(0, "fflags");
     instruction_->AppendDestination(flag_op);
     AppendVectorRegisterOperands({kVmask}, {});
@@ -797,7 +797,7 @@
     Vs2 vs2_value[vs2_size * 8];
     auto vs2_span = Span<Vs2>(vs2_value);
     AppendVectorRegisterOperands({kVs2}, {kVd});
-    AppendRegisterOperands({kFs1Name}, {});
+    AppendRegisterOperands<Fs1>({kFs1Name}, {});
     AppendVectorRegisterOperands({kVmask}, {});
     SetVectorRegisterValues<uint8_t>(
         {{kVmaskName, Span<const uint8_t>(kA5Mask)}});
diff --git a/riscv/test/riscv_vector_fp_unary_instructions_test.cc b/riscv/test/riscv_vector_fp_unary_instructions_test.cc
index 6fb2b30..e3ee470 100644
--- a/riscv/test/riscv_vector_fp_unary_instructions_test.cc
+++ b/riscv/test/riscv_vector_fp_unary_instructions_test.cc
@@ -610,7 +610,7 @@
 // Test vfmv.f.s instruction - move element 0 to scalar fp register.
 TEST_F(RiscVFPUnaryInstructionsTest, VfmvToScalar) {
   SetSemanticFunction(&Vfmvfs);
-  AppendRegisterOperands({}, {kFs1Name});
+  AppendRegisterOperands<RVFpRegister>({}, {kFs1Name});
   AppendVectorRegisterOperands({kVs2}, {});
   for (int byte_sew : {1, 2, 4, 8}) {
     int vlen = kVectorLengthInBytes / byte_sew;
@@ -650,7 +650,7 @@
 // Test vfmv.f.s instruction - move scalar fp register to element 0.
 TEST_F(RiscVFPUnaryInstructionsTest, VfmvFromScalar) {
   SetSemanticFunction(&Vfmvsf);
-  AppendRegisterOperands({kFs1Name}, {});
+  AppendRegisterOperands<RVFpRegister>({kFs1Name}, {});
   AppendVectorRegisterOperands({}, {kVd});
   for (int byte_sew : {1, 2, 4, 8}) {
     int vlen = kVectorLengthInBytes / byte_sew;
diff --git a/riscv/test/riscv_vector_instructions_test_base.h b/riscv/test/riscv_vector_instructions_test_base.h
index a243929..7ba43ce 100644
--- a/riscv/test/riscv_vector_instructions_test_base.h
+++ b/riscv/test/riscv_vector_instructions_test_base.h
@@ -180,6 +180,7 @@
 
   // Creates source and destination scalar register operands for the registers
   // named in the two vectors and append them to the given instruction.
+  template <typename T>
   void AppendRegisterOperands(Instruction *inst,
                               const std::vector<std::string> &sources,
                               const std::vector<std::string> &destinations) {
@@ -195,9 +196,10 @@
 
   // Creates source and destination scalar register operands for the registers
   // named in the two vectors and append them to the default instruction.
+  template <typename T>
   void AppendRegisterOperands(const std::vector<std::string> &sources,
                               const std::vector<std::string> &destinations) {
-    AppendRegisterOperands(instruction_, sources, destinations);
+    AppendRegisterOperands<T>(instruction_, sources, destinations);
   }
 
   // Returns the value of the named vector register.
@@ -564,7 +566,7 @@
     Vs2 vs2_value[vs2_size * 8];
     auto vs2_span = Span<Vs2>(vs2_value);
     AppendVectorRegisterOperands({kVs2}, {});
-    AppendRegisterOperands({kRs1Name}, {});
+    AppendRegisterOperands<Rs1>({kRs1Name}, {});
     AppendVectorRegisterOperands({kVmask}, {kVd});
     // Initialize input values.
     FillArrayWithRandomValues<Vs2>(vs2_span);
@@ -833,7 +835,7 @@
     Vs2 vs2_value[vs2_size * 8];
     auto vs2_span = Span<Vs2>(vs2_value);
     AppendVectorRegisterOperands({kVs2}, {});
-    AppendRegisterOperands({kRs1Name}, {});
+    AppendRegisterOperands<Rs1>({kRs1Name}, {});
     AppendVectorRegisterOperands({kVd, kVmask}, {kVd});
     // Initialize input values.
     FillArrayWithRandomValues<Vd>(vd_span);
@@ -1090,7 +1092,7 @@
     Vs2 vs2_value[vs2_size * 8];
     auto vs2_span = Span<Vs2>(vs2_value);
     AppendVectorRegisterOperands({kVs2}, {});
-    AppendRegisterOperands({kRs1Name}, {});
+    AppendRegisterOperands<Rs1>({kRs1Name}, {});
     AppendVectorRegisterOperands({kVmask}, {kVd});
     // Initialize input values.
     FillArrayWithRandomValues<Vs2>(vs2_span);
diff --git a/riscv/test/riscv_vector_permute_instructions_test.cc b/riscv/test/riscv_vector_permute_instructions_test.cc
index 5cebdab..d649d59 100644
--- a/riscv/test/riscv_vector_permute_instructions_test.cc
+++ b/riscv/test/riscv_vector_permute_instructions_test.cc
@@ -14,6 +14,8 @@
 
 #include "riscv/riscv_vector_permute_instructions.h"
 
+#include <cstdint>
+
 #include "absl/random/random.h"
 #include "googlemock/include/gmock/gmock.h"
 #include "mpact/sim/generic/instruction.h"
@@ -208,7 +210,7 @@
 TEST_F(RiscVVectorPermuteInstructionsTest, VrgatherVS8) {
   SetSemanticFunction(&Vrgather);
   AppendVectorRegisterOperands({kVs2}, {});
-  AppendRegisterOperands({kRs1Name}, {});
+  AppendRegisterOperands<RV32Register>({kRs1Name}, {});
   AppendVectorRegisterOperands({kVmask}, {kVd});
   VrgatherVSHelper<uint8_t>(this, instruction_);
 }
@@ -216,7 +218,7 @@
 TEST_F(RiscVVectorPermuteInstructionsTest, VrgatherVS16) {
   SetSemanticFunction(&Vrgather);
   AppendVectorRegisterOperands({kVs2}, {});
-  AppendRegisterOperands({kRs1Name}, {});
+  AppendRegisterOperands<RV32Register>({kRs1Name}, {});
   AppendVectorRegisterOperands({kVmask}, {kVd});
   VrgatherVSHelper<uint16_t>(this, instruction_);
 }
@@ -224,7 +226,7 @@
 TEST_F(RiscVVectorPermuteInstructionsTest, VrgatherVS32) {
   SetSemanticFunction(&Vrgather);
   AppendVectorRegisterOperands({kVs2}, {});
-  AppendRegisterOperands({kRs1Name}, {});
+  AppendRegisterOperands<RV32Register>({kRs1Name}, {});
   AppendVectorRegisterOperands({kVmask}, {kVd});
   VrgatherVSHelper<uint32_t>(this, instruction_);
 }
@@ -232,7 +234,7 @@
 TEST_F(RiscVVectorPermuteInstructionsTest, VrgatherVS64) {
   SetSemanticFunction(&Vrgather);
   AppendVectorRegisterOperands({kVs2}, {});
-  AppendRegisterOperands({kRs1Name}, {});
+  AppendRegisterOperands<RV32Register>({kRs1Name}, {});
   AppendVectorRegisterOperands({kVmask}, {kVd});
   VrgatherVSHelper<uint64_t>(this, instruction_);
 }
@@ -347,7 +349,7 @@
 TEST_F(RiscVVectorPermuteInstructionsTest, Vslideup8) {
   SetSemanticFunction(&Vslideup);
   AppendVectorRegisterOperands({kVs2}, {});
-  AppendRegisterOperands({kRs1Name}, {});
+  AppendRegisterOperands<RV32Register>({kRs1Name}, {});
   AppendVectorRegisterOperands({kVmask}, {kVd});
   SlideHelper<uint8_t>(this, instruction_, /*is_slide_up*/ true);
 }
@@ -355,7 +357,7 @@
 TEST_F(RiscVVectorPermuteInstructionsTest, Vslideup16) {
   SetSemanticFunction(&Vslideup);
   AppendVectorRegisterOperands({kVs2}, {});
-  AppendRegisterOperands({kRs1Name}, {});
+  AppendRegisterOperands<RV32Register>({kRs1Name}, {});
   AppendVectorRegisterOperands({kVmask}, {kVd});
   SlideHelper<uint16_t>(this, instruction_, /*is_slide_up*/ true);
 }
@@ -363,7 +365,7 @@
 TEST_F(RiscVVectorPermuteInstructionsTest, Vslideup32) {
   SetSemanticFunction(&Vslideup);
   AppendVectorRegisterOperands({kVs2}, {});
-  AppendRegisterOperands({kRs1Name}, {});
+  AppendRegisterOperands<RV32Register>({kRs1Name}, {});
   AppendVectorRegisterOperands({kVmask}, {kVd});
   SlideHelper<uint32_t>(this, instruction_, /*is_slide_up*/ true);
 }
@@ -371,7 +373,7 @@
 TEST_F(RiscVVectorPermuteInstructionsTest, Vslideup64) {
   SetSemanticFunction(&Vslideup);
   AppendVectorRegisterOperands({kVs2}, {});
-  AppendRegisterOperands({kRs1Name}, {});
+  AppendRegisterOperands<RV32Register>({kRs1Name}, {});
   AppendVectorRegisterOperands({kVmask}, {kVd});
   SlideHelper<uint64_t>(this, instruction_, /*is_slide_up*/ true);
 }
@@ -380,7 +382,7 @@
 TEST_F(RiscVVectorPermuteInstructionsTest, Vslidedown8) {
   SetSemanticFunction(&Vslidedown);
   AppendVectorRegisterOperands({kVs2}, {});
-  AppendRegisterOperands({kRs1Name}, {});
+  AppendRegisterOperands<RV32Register>({kRs1Name}, {});
   AppendVectorRegisterOperands({kVmask}, {kVd});
   SlideHelper<uint8_t>(this, instruction_, /*is_slide_up*/ false);
 }
@@ -388,7 +390,7 @@
 TEST_F(RiscVVectorPermuteInstructionsTest, Vslidedown16) {
   SetSemanticFunction(&Vslidedown);
   AppendVectorRegisterOperands({kVs2}, {});
-  AppendRegisterOperands({kRs1Name}, {});
+  AppendRegisterOperands<RV32Register>({kRs1Name}, {});
   AppendVectorRegisterOperands({kVmask}, {kVd});
   SlideHelper<uint16_t>(this, instruction_, /*is_slide_up*/ false);
 }
@@ -396,7 +398,7 @@
 TEST_F(RiscVVectorPermuteInstructionsTest, Vslidedown32) {
   SetSemanticFunction(&Vslidedown);
   AppendVectorRegisterOperands({kVs2}, {});
-  AppendRegisterOperands({kRs1Name}, {});
+  AppendRegisterOperands<RV32Register>({kRs1Name}, {});
   AppendVectorRegisterOperands({kVmask}, {kVd});
   SlideHelper<uint32_t>(this, instruction_, /*is_slide_up*/ false);
 }
@@ -404,7 +406,7 @@
 TEST_F(RiscVVectorPermuteInstructionsTest, Vslidedown64) {
   SetSemanticFunction(&Vslidedown);
   AppendVectorRegisterOperands({kVs2}, {});
-  AppendRegisterOperands({kRs1Name}, {});
+  AppendRegisterOperands<RV32Register>({kRs1Name}, {});
   AppendVectorRegisterOperands({kVmask}, {kVd});
   SlideHelper<uint64_t>(this, instruction_, /*is_slide_up*/ false);
 }
@@ -486,7 +488,7 @@
 TEST_F(RiscVVectorPermuteInstructionsTest, Vslide1up8) {
   SetSemanticFunction(&Vslide1up);
   AppendVectorRegisterOperands({kVs2}, {});
-  AppendRegisterOperands({kRs1Name}, {});
+  AppendRegisterOperands<RV32Register>({kRs1Name}, {});
   AppendVectorRegisterOperands({kVmask}, {kVd});
   Slide1Helper<uint8_t>(this, instruction_, /*is_slide_up*/ true);
 }
@@ -494,7 +496,7 @@
 TEST_F(RiscVVectorPermuteInstructionsTest, Vslide1up16) {
   SetSemanticFunction(&Vslide1up);
   AppendVectorRegisterOperands({kVs2}, {});
-  AppendRegisterOperands({kRs1Name}, {});
+  AppendRegisterOperands<RV32Register>({kRs1Name}, {});
   AppendVectorRegisterOperands({kVmask}, {kVd});
   Slide1Helper<uint16_t>(this, instruction_, /*is_slide_up*/ true);
 }
@@ -502,7 +504,7 @@
 TEST_F(RiscVVectorPermuteInstructionsTest, Vslide1up32) {
   SetSemanticFunction(&Vslide1up);
   AppendVectorRegisterOperands({kVs2}, {});
-  AppendRegisterOperands({kRs1Name}, {});
+  AppendRegisterOperands<RV32Register>({kRs1Name}, {});
   AppendVectorRegisterOperands({kVmask}, {kVd});
   Slide1Helper<uint32_t>(this, instruction_, /*is_slide_up*/ true);
 }
@@ -510,7 +512,7 @@
 TEST_F(RiscVVectorPermuteInstructionsTest, Vslide1up64) {
   SetSemanticFunction(&Vslide1up);
   AppendVectorRegisterOperands({kVs2}, {});
-  AppendRegisterOperands({kRs1Name}, {});
+  AppendRegisterOperands<RV32Register>({kRs1Name}, {});
   AppendVectorRegisterOperands({kVmask}, {kVd});
   Slide1Helper<uint64_t>(this, instruction_, /*is_slide_up*/ true);
 }
@@ -518,7 +520,7 @@
 TEST_F(RiscVVectorPermuteInstructionsTest, Vslide1down8) {
   SetSemanticFunction(&Vslide1down);
   AppendVectorRegisterOperands({kVs2}, {});
-  AppendRegisterOperands({kRs1Name}, {});
+  AppendRegisterOperands<RV32Register>({kRs1Name}, {});
   AppendVectorRegisterOperands({kVmask}, {kVd});
   Slide1Helper<uint8_t>(this, instruction_, /*is_slide_up*/ false);
 }
@@ -526,7 +528,7 @@
 TEST_F(RiscVVectorPermuteInstructionsTest, Vslide1down16) {
   SetSemanticFunction(&Vslide1down);
   AppendVectorRegisterOperands({kVs2}, {});
-  AppendRegisterOperands({kRs1Name}, {});
+  AppendRegisterOperands<RV32Register>({kRs1Name}, {});
   AppendVectorRegisterOperands({kVmask}, {kVd});
   Slide1Helper<uint16_t>(this, instruction_, /*is_slide_up*/ false);
 }
@@ -534,7 +536,7 @@
 TEST_F(RiscVVectorPermuteInstructionsTest, Vslide1down32) {
   SetSemanticFunction(&Vslide1down);
   AppendVectorRegisterOperands({kVs2}, {});
-  AppendRegisterOperands({kRs1Name}, {});
+  AppendRegisterOperands<RV32Register>({kRs1Name}, {});
   AppendVectorRegisterOperands({kVmask}, {kVd});
   Slide1Helper<uint32_t>(this, instruction_, /*is_slide_up*/ false);
 }
@@ -542,7 +544,7 @@
 TEST_F(RiscVVectorPermuteInstructionsTest, Vslide1down64) {
   SetSemanticFunction(&Vslide1down);
   AppendVectorRegisterOperands({kVs2}, {});
-  AppendRegisterOperands({kRs1Name}, {});
+  AppendRegisterOperands<RV32Register>({kRs1Name}, {});
   AppendVectorRegisterOperands({kVmask}, {kVd});
   Slide1Helper<uint64_t>(this, instruction_, /*is_slide_up*/ false);
 }
diff --git a/riscv/test/riscv_vector_unary_instructions_test.cc b/riscv/test/riscv_vector_unary_instructions_test.cc
index db3e21e..84953ea 100644
--- a/riscv/test/riscv_vector_unary_instructions_test.cc
+++ b/riscv/test/riscv_vector_unary_instructions_test.cc
@@ -101,7 +101,7 @@
 // Test move vector element 0 to scalar register.
 TEST_F(RiscVVectorUnaryInstructionsTest, VmvToScalar) {
   SetSemanticFunction(&VmvToScalar);
-  AppendRegisterOperands({}, {kRs1Name});
+  AppendRegisterOperands<RV32Register>({}, {kRs1Name});
   AppendVectorRegisterOperands({kVs2}, {});
   for (int byte_sew : {1, 2, 4, 8}) {
     int vlen = kVectorLengthInBytes / byte_sew;
@@ -151,7 +151,7 @@
 // Test move scalar to vector element 0.
 TEST_F(RiscVVectorUnaryInstructionsTest, VmvFromScalar) {
   SetSemanticFunction(&VmvFromScalar);
-  AppendRegisterOperands({kRs1Name}, {});
+  AppendRegisterOperands<RV32Register>({kRs1Name}, {});
   AppendVectorRegisterOperands({}, {kVs2});
   for (int byte_sew : {1, 2, 4, 8}) {
     int vlen = kVectorLengthInBytes / byte_sew;
@@ -160,6 +160,23 @@
     ConfigureVectorUnit(vtype, vlen);
     // Test 10 different values.
     for (int i = 0; i < 10; i++) {
+      // Set the vector register to known values.
+      for (int v = 0; v < vlen; v++) {
+        switch (byte_sew) {
+          case 1:
+            vreg_[kVs2]->data_buffer()->Set<int8_t>(v, v);
+            break;
+          case 2:
+            vreg_[kVs2]->data_buffer()->Set<int16_t>(v, v);
+            break;
+          case 4:
+            vreg_[kVs2]->data_buffer()->Set<int32_t>(v, v);
+            break;
+          case 8:
+            vreg_[kVs2]->data_buffer()->Set<int64_t>(v, v);
+            break;
+        }
+      }
       auto value = RandomValue<SignedXregType>();
       SetRegisterValues<SignedXregType>({{kRs1Name, value}});
       instruction_->Execute();
@@ -167,18 +184,30 @@
         case 1:
           EXPECT_EQ(vreg_[kVs2]->data_buffer()->Get<int8_t>(0),
                     static_cast<int8_t>(value));
+          for (int v = 1; v < vlen; v++) {
+            EXPECT_EQ(vreg_[kVs2]->data_buffer()->Get<int8_t>(v), v);
+          }
           break;
         case 2:
           EXPECT_EQ(vreg_[kVs2]->data_buffer()->Get<int16_t>(0),
                     static_cast<int16_t>(value));
+          for (int v = 1; v < vlen; v++) {
+            EXPECT_EQ(vreg_[kVs2]->data_buffer()->Get<int16_t>(v), v);
+          }
           break;
         case 4:
           EXPECT_EQ(vreg_[kVs2]->data_buffer()->Get<int32_t>(0),
                     static_cast<int32_t>(value));
+          for (int v = 1; v < vlen; v++) {
+            EXPECT_EQ(vreg_[kVs2]->data_buffer()->Get<int32_t>(v), v);
+          }
           break;
         case 8:
           EXPECT_EQ(vreg_[kVs2]->data_buffer()->Get<int64_t>(0),
                     static_cast<int64_t>(value));
+          for (int v = 1; v < vlen; v++) {
+            EXPECT_EQ(vreg_[kVs2]->data_buffer()->Get<int64_t>(v), v);
+          }
           break;
       }
     }
@@ -190,7 +219,7 @@
   uint32_t vtype = (kSewSettingsByByteSize[1] << 3) | kLmulSettingByLogSize[7];
   SetSemanticFunction(&Vcpop);
   AppendVectorRegisterOperands({kVs2, kVmask}, {});
-  AppendRegisterOperands({}, {kRdName});
+  AppendRegisterOperands<RV32Register>({}, {kRdName});
   for (int vlen : {1, 8, 32, 48, 127, 200}) {
     ConfigureVectorUnit(vtype, vlen);
     // All 1s for mask and vector.
@@ -211,7 +240,7 @@
 TEST_F(RiscVVectorUnaryInstructionsTest, Vfirst) {
   SetSemanticFunction(&Vfirst);
   AppendVectorRegisterOperands({kVs2, kVmask}, {});
-  AppendRegisterOperands({}, {kRdName});
+  AppendRegisterOperands<RV32Register>({}, {kRdName});
   uint8_t reg_value[kVectorLengthInBytes];
   // Set vtype to byte vector, and vector lmul to 8.
   uint32_t vtype = (kSewSettingsByByteSize[1] << 3) | kLmulSettingByLogSize[7];
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_