Fixed errors in fmin and clmulh.

Updated simulator targets to have the same set of options.

PiperOrigin-RevId: 694530007
Change-Id: Ib2c9952db83c4343606e7abe199b40eba8885fdb
diff --git a/riscv/BUILD b/riscv/BUILD
index da0d359..d2b0aa6 100644
--- a/riscv/BUILD
+++ b/riscv/BUILD
@@ -313,11 +313,14 @@
     src = "riscv32v.isa",
     includes = [
         "riscv32g.isa",
+        "riscv32gzb.isa",
+        "riscv32zb.isa",
         "riscv_vector.isa",
     ],
     isa_name = "RiscV32GV",
     prefix = "riscv32gv",
     deps = [
+        ":riscv_bitmanip_instructions",
         ":riscv_g",
         ":riscv_v",
         "@com_google_absl//absl/functional:bind_front",
@@ -330,6 +333,7 @@
     decoder_name = "RiscV32GV",
     includes = [
         "riscv32g.bin_fmt",
+        "riscv32zb.bin_fmt",
         "riscv_vector.bin_fmt",
     ],
     prefix = "riscv32gv",
@@ -370,6 +374,40 @@
 )
 
 mpact_isa_decoder(
+    name = "riscv32gvzb_isa",
+    src = "riscv32v.isa",
+    includes = [
+        "riscv32g.isa",
+        "riscv32gzb.isa",
+        "riscv32zb.isa",
+        "riscv_vector.isa",
+    ],
+    isa_name = "RiscV32GVZB",
+    prefix = "riscv32gvzb",
+    deps = [
+        ":riscv_bitmanip_instructions",
+        ":riscv_g",
+        ":riscv_v",
+        "@com_google_absl//absl/functional:bind_front",
+    ],
+)
+
+mpact_bin_fmt_decoder(
+    name = "riscv32gvzb_bin_fmt",
+    src = "riscv32v.bin_fmt",
+    decoder_name = "RiscV32GVZB",
+    includes = [
+        "riscv32g.bin_fmt",
+        "riscv32zb.bin_fmt",
+        "riscv_vector.bin_fmt",
+    ],
+    prefix = "riscv32gvzb",
+    deps = [
+        ":riscv32gvzb_isa",
+    ],
+)
+
+mpact_isa_decoder(
     name = "rvm23_isa",
     src = "rvm23.isa",
     includes = [
@@ -686,6 +724,40 @@
 )
 
 cc_library(
+    name = "riscv32gzb_vec_decoder",
+    srcs = [
+        "riscv32gzb_vec_decoder.cc",
+        "riscv32gzb_vec_encoding.cc",
+    ],
+    hdrs = [
+        "riscv32gzb_vec_decoder.h",
+        "riscv32gzb_vec_encoding.h",
+    ],
+    copts = ["-O3"],
+    deps = [
+        ":riscv32gvzb_bin_fmt",
+        ":riscv32gvzb_isa",
+        ":riscv_encoding_common",
+        ":riscv_getters",
+        ":riscv_state",
+        "@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 = "riscv64g_decoder",
     srcs = [
         "riscv64_decoder.cc",
@@ -794,6 +866,7 @@
         "@com_google_absl//absl/functional:bind_front",
         "@com_google_absl//absl/log",
         "@com_google_absl//absl/log:check",
+        "@com_google_absl//absl/numeric:bits",
         "@com_google_absl//absl/status",
         "@com_google_absl//absl/status:statusor",
         "@com_google_absl//absl/strings",
@@ -894,6 +967,7 @@
     copts = ["-O3"],
     deps = [
         ":riscv_debug_interface",
+        ":riscv_top",
         ":stoull_wrapper",
         "@com_google_absl//absl/container:btree",
         "@com_google_absl//absl/container:flat_hash_set",
@@ -954,10 +1028,12 @@
         ":riscv_fp_state",
         ":riscv_state",
         ":riscv_top",
+        "@com_google_absl//absl/base:log_severity",
         "@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/log:globals",
         "@com_google_absl//absl/strings",
         "@com_google_absl//absl/strings:str_format",
         "@com_google_absl//absl/time",
@@ -983,17 +1059,21 @@
         ":debug_command_shell",
         ":riscv32_htif_semihost",
         ":riscv32g_vec_decoder",
+        ":riscv32gzb_vec_decoder",
         ":riscv_arm_semihost",
         ":riscv_fp_state",
         ":riscv_state",
         ":riscv_top",
+        "@com_google_absl//absl/base:log_severity",
         "@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/log:globals",
         "@com_google_absl//absl/strings",
         "@com_google_absl//absl/strings:str_format",
         "@com_google_absl//absl/time",
+        "@com_google_mpact-sim//mpact/sim/generic:core",
         "@com_google_mpact-sim//mpact/sim/generic:core_debug_interface",
         "@com_google_mpact-sim//mpact/sim/generic:counters",
         "@com_google_mpact-sim//mpact/sim/generic:instruction",
@@ -1018,10 +1098,12 @@
         ":riscv_fp_state",
         ":riscv_state",
         ":riscv_top",
+        "@com_google_absl//absl/base:log_severity",
         "@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/log:globals",
         "@com_google_absl//absl/strings",
         "@com_google_absl//absl/strings:str_format",
         "@com_google_absl//absl/time",
@@ -1050,10 +1132,12 @@
         ":riscv_fp_state",
         ":riscv_state",
         ":riscv_top",
+        "@com_google_absl//absl/base:log_severity",
         "@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/log:globals",
         "@com_google_absl//absl/strings",
         "@com_google_absl//absl/strings:str_format",
         "@com_google_absl//absl/time",
diff --git a/riscv/debug_command_shell.cc b/riscv/debug_command_shell.cc
index 4147d99..6afea13 100644
--- a/riscv/debug_command_shell.cc
+++ b/riscv/debug_command_shell.cc
@@ -35,6 +35,7 @@
 #include "mpact/sim/generic/type_helpers.h"
 #include "re2/re2.h"
 #include "riscv/riscv_debug_interface.h"
+#include "riscv/riscv_top.h"
 #include "riscv/stoull_wrapper.h"
 
 namespace mpact {
@@ -83,6 +84,7 @@
       disable_action_n_re_{R"(\s*action\s+disable\s+\*(\d+)\s*)"},
       clear_action_n_re_{R"(\s*action\s+clear\s+\*(\d+)\s*)"},
       clear_all_action_re_{R"(\s*action\s+clear-all\s*)"},
+      branch_trace_re_{R"(\s*branch-trace\s*)"},
       exec_re_{R"(\s*exec\s+(.+)\s*)"},
       empty_re_{R"(\s*(?:\#.*)?)"},
       help_re_{R"(\s*help\s*)"} {
@@ -151,6 +153,8 @@
   action clear #<N>                - clear action point with index N.
   action clear-all                 - clear all action points.
   action                           - list action points.
+  branch-trace                     - list the control flow change (includes
+                                     interrupts) w/out repetitions due to loops.
   exec    NAME                     - load commands from file 'NAME' and execute
                                      each line as a command. Lines starting with
                                      a '#' are treated as comments.
@@ -1000,6 +1004,52 @@
       continue;
     }
 
+    // branch-trace.
+    // This prints out a list of the last "branch trace size" pairs of <from,
+    // to> control flow changes (including interrupts), with no repetitions for
+    // loops.
+    if (RE2::FullMatch(line_view, *branch_trace_re_)) {
+      // Get the index of the head of the queue.
+      auto head_result =
+          core_access_[current_core_].debug_interface->ReadRegister(
+              "$branch_trace_head");
+      if (!head_result.ok()) {
+        os << "Error: " << head_result.status().message() << "\n";
+        continue;
+      }
+      // Adjust by one, as the head points to the most recent valid entry.
+      auto head = head_result.value() + 1;
+      // Get the branch trace data buffer.
+      auto result =
+          core_access_[current_core_].debug_interface->GetRegisterDataBuffer(
+              "$branch_trace");
+      if (!result.ok()) {
+        os << "Error: " << result.status().message() << "\n";
+        continue;
+      }
+      auto *db = result.value();
+      // Check for null data buffer.
+      if (db == nullptr) {
+        os << "Error: register '$branch_trace' has no data buffer\n";
+        os.flush();
+        continue;
+      }
+      // Get a span for the branch trace.
+      auto trace_span = db->Get<BranchTraceEntry>();
+      auto size = trace_span.size();
+      os << absl::StrFormat("     %-8s      %-8s     %8s\n", "From", "To",
+                            "Count");
+      for (int i = 0; i < size; ++i) {
+        auto index = (head + i) % size;
+        auto [from, to, count] = trace_span[index];
+        // Ignore 0 -> 0 entries. Those are the initial values.
+        if (count == 0) continue;
+        os << absl::StrFormat("   0x%08x -> 0x%08x     %8u\n", from, to, count);
+      }
+      os.flush();
+      continue;
+    }
+
     if (std::string file_name;
         RE2::FullMatch(line_view, *exec_re_, &file_name)) {
       auto *ifile = new std::ifstream(file_name);
diff --git a/riscv/riscv32gzb_vec_decoder.cc b/riscv/riscv32gzb_vec_decoder.cc
new file mode 100644
index 0000000..cdef792
--- /dev/null
+++ b/riscv/riscv32gzb_vec_decoder.cc
@@ -0,0 +1,104 @@
+// Copyright 2023 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/riscv32gzb_vec_decoder.h"
+
+#include <cstdint>
+#include <memory>
+#include <new>
+#include <string>
+
+#include "mpact/sim/generic/instruction.h"
+#include "mpact/sim/generic/program_error.h"
+#include "mpact/sim/generic/type_helpers.h"
+#include "mpact/sim/util/memory/memory_interface.h"
+#include "riscv/riscv32gvzb_decoder.h"
+#include "riscv/riscv32gvzb_enums.h"
+#include "riscv/riscv32gzb_vec_encoding.h"
+#include "riscv/riscv_state.h"
+
+namespace mpact {
+namespace sim {
+namespace riscv {
+
+using ::mpact::sim::generic::operator*;  // NOLINT: is used below (clang error).
+
+RiscV32GZBVecDecoder::RiscV32GZBVecDecoder(RiscVState *state,
+                                           util::MemoryInterface *memory)
+    : state_(state),
+      memory_(memory),
+      inst_db_(state_->db_factory()->Allocate<uint32_t>(1)) {
+  // Get a handle to the internal error in the program error controller.
+  decode_error_ = state->program_error_controller()->GetProgramError(
+      generic::ProgramErrorController::kInternalErrorName);
+
+  // Allocate the isa factory class, the top level isa decoder instance, and
+  // the encoding parser.
+  riscv_isa_factory_ = std::make_unique<RV32GVZBIsaFactory>();
+  riscv_isa_ = std::make_unique<isa32gvzb::RiscV32GVZBInstructionSet>(
+      state, riscv_isa_factory_.get());
+  riscv_encoding_ = std::make_unique<isa32gvzb::RiscV32GZBVecEncoding>(state);
+  decode_error_ = state->program_error_controller()->GetProgramError(
+      generic::ProgramErrorController::kInternalErrorName);
+}
+
+RiscV32GZBVecDecoder::~RiscV32GZBVecDecoder() { inst_db_->DecRef(); }
+
+generic::Instruction *RiscV32GZBVecDecoder::DecodeInstruction(
+    uint64_t address) {
+  // First check that the address is aligned properly. If not, create and return
+  // an instruction object that will raise an exception.
+  if (address & 0x1) {
+    auto *inst = new generic::Instruction(address, state_);
+    inst->set_size(1);
+    inst->SetDisassemblyString("Misaligned instruction address");
+    inst->set_opcode(*isa32gvzb::OpcodeEnum::kNone);
+    inst->set_address(address);
+    inst->set_semantic_function([this, address](generic::Instruction *inst) {
+      state_->Trap(/*is_interrupt*/ false, address,
+                   *ExceptionCode::kInstructionAddressMisaligned, address ^ 0x1,
+                   inst);
+    });
+    return inst;
+  }
+
+  // If the address is greater than the max address, return an instruction
+  // object that will raise an exception.
+  if (address > state_->max_physical_address()) {
+    auto *inst = new generic::Instruction(address, state_);
+    inst->set_size(0);
+    inst->SetDisassemblyString("Instruction access fault");
+    inst->set_opcode(*isa32gvzb::OpcodeEnum::kNone);
+    inst->set_address(address);
+    inst->set_semantic_function([this, address](generic::Instruction *inst) {
+      state_->Trap(/*is_interrupt*/ false, address,
+                   *ExceptionCode::kInstructionAccessFault, address, nullptr);
+    });
+    return inst;
+  }
+
+  // Read the instruction word from memory and parse it in the encoding parser.
+  memory_->Load(address, inst_db_, nullptr, nullptr);
+  const uint32_t iword = inst_db_->Get<uint32_t>(0);
+  riscv_encoding_->ParseInstruction(iword);
+
+  // Call the isa decoder to obtain a new instruction object for the instruction
+  // word that was parsed above.
+  auto *instruction = riscv_isa_->Decode(address, riscv_encoding_.get());
+  return instruction;
+}
+
+}  // namespace riscv
+}  // namespace sim
+}  // namespace mpact
diff --git a/riscv/riscv32gzb_vec_decoder.h b/riscv/riscv32gzb_vec_decoder.h
new file mode 100644
index 0000000..4ecb035
--- /dev/null
+++ b/riscv/riscv32gzb_vec_decoder.h
@@ -0,0 +1,90 @@
+// Copyright 2023 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_RISCV32GZB_VEC_DECODER_H_
+#define THIRD_PARTY_MPACT_RISCV_RISCV32GZB_VEC_DECODER_H_
+
+#include <cstdint>
+#include <memory>
+
+#include "mpact/sim/generic/data_buffer.h"
+#include "mpact/sim/generic/decoder_interface.h"
+#include "mpact/sim/generic/instruction.h"
+#include "mpact/sim/generic/program_error.h"
+#include "mpact/sim/generic/type_helpers.h"
+#include "mpact/sim/util/memory/memory_interface.h"
+#include "riscv/riscv32gvzb_decoder.h"
+#include "riscv/riscv32gvzb_enums.h"
+#include "riscv/riscv32gzb_vec_encoding.h"
+#include "riscv/riscv_state.h"
+
+namespace mpact {
+namespace sim {
+namespace riscv {
+
+using ::mpact::sim::generic::operator*;  // NOLINT: clang-tidy false positive.
+
+// This is the factory class needed by the generated decoder. It is responsible
+// for creating the decoder for each slot instance. Since the riscv architecture
+// only has a single slot, it's a pretty simple class.
+class RV32GVZBIsaFactory : public isa32gvzb::RiscV32GVZBInstructionSetFactory {
+ public:
+  std::unique_ptr<isa32gvzb::Riscv32gvzbSlot> CreateRiscv32gvzbSlot(
+      ArchState *state) override {
+    return std::make_unique<isa32gvzb::Riscv32gvzbSlot>(state);
+  }
+};
+
+// This class implements the generic DecoderInterface and provides a bridge
+// to the (isa specific) generated decoder classes. It implements a decoder that
+// includes the RV32GZB + vector extensions.
+class RiscV32GZBVecDecoder : public generic::DecoderInterface {
+ public:
+  using SlotEnum = isa32gvzb::SlotEnum;
+  using OpcodeEnum = isa32gvzb::OpcodeEnum;
+
+  RiscV32GZBVecDecoder(RiscVState *state, util::MemoryInterface *memory);
+  RiscV32GZBVecDecoder() = delete;
+  ~RiscV32GZBVecDecoder() override;
+
+  // This will always return a valid instruction that can be executed. In the
+  // case of a decode error, the semantic function in the instruction object
+  // instance will raise an internal simulator error when executed.
+  generic::Instruction *DecodeInstruction(uint64_t address) override;
+  // Return the number of opcodes supported by this decoder.
+  int GetNumOpcodes() const override { return *OpcodeEnum::kPastMaxValue; }
+  // Return the name of the opcode at the given index.
+  const char *GetOpcodeName(int index) const override {
+    return isa32gvzb::kOpcodeNames[index];
+  }
+
+ private:
+  RiscVState *const state_;
+  util::MemoryInterface *const memory_;
+
+  // Buffer used to load instructions from memory. Re-used for each instruction
+  // word.
+  generic::DataBuffer *const inst_db_;
+
+  std::unique_ptr<generic::ProgramError> decode_error_;
+  std::unique_ptr<isa32gvzb::RiscV32GZBVecEncoding> riscv_encoding_;
+  std::unique_ptr<RV32GVZBIsaFactory> riscv_isa_factory_;
+  std::unique_ptr<isa32gvzb::RiscV32GVZBInstructionSet> riscv_isa_;
+};
+
+}  // namespace riscv
+}  // namespace sim
+}  // namespace mpact
+
+#endif  // THIRD_PARTY_MPACT_RISCV_RISCV32GZB_VEC_DECODER_H_
diff --git a/riscv/riscv32gzb_vec_encoding.cc b/riscv/riscv32gzb_vec_encoding.cc
new file mode 100644
index 0000000..ea62791
--- /dev/null
+++ b/riscv/riscv32gzb_vec_encoding.cc
@@ -0,0 +1,181 @@
+// 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/riscv32gzb_vec_encoding.h"
+
+#include <cstdint>
+
+#include "absl/log/log.h"
+#include "absl/strings/str_cat.h"
+#include "mpact/sim/generic/simple_resource.h"
+#include "mpact/sim/generic/simple_resource_operand.h"
+#include "mpact/sim/generic/type_helpers.h"
+#include "riscv/riscv32gvzb_bin_decoder.h"
+#include "riscv/riscv32gvzb_decoder.h"
+#include "riscv/riscv32gvzb_enums.h"
+#include "riscv/riscv_encoding_common.h"
+#include "riscv/riscv_getters.h"
+#include "riscv/riscv_getters_rv32.h"
+#include "riscv/riscv_getters_vector.h"
+#include "riscv/riscv_getters_zba.h"
+#include "riscv/riscv_getters_zbb32.h"
+#include "riscv/riscv_register.h"
+#include "riscv/riscv_state.h"
+
+namespace mpact::sim::riscv::isa32gvzb {
+
+using ::mpact::sim::generic::operator*;  // NOLINT: clang-tidy false positive.
+
+RiscV32GZBVecEncoding::RiscV32GZBVecEncoding(RiscVState *state)
+    : state_(state),
+      inst_word_(0),
+      opcode_(OpcodeEnum::kNone),
+      format_(FormatEnum::kNone) {
+  resource_pool_ = new generic::SimpleResourcePool("RiscV32GVZB", 128);
+  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; });
+  // Add the operand getters for the base instruction set.
+  AddRiscVSourceGetters<SourceOpEnum, Extractors, RV32Register, RVFpRegister>(
+      source_op_getters_, this);
+  AddRiscVDestGetters<DestOpEnum, Extractors, RV32Register, RVFpRegister>(
+      dest_op_getters_, this);
+  AddRiscVSimpleResourceGetters<SimpleResourceEnum, Extractors>(
+      simple_resource_getters_, this);
+  // Add operand getters for 32 bit version not in the base instruction set.
+  AddRiscV32SourceGetters<SourceOpEnum, Extractors, RV32Register, RVFpRegister>(
+      source_op_getters_, this);
+  AddRiscV32DestGetters<DestOpEnum, Extractors, RV32Register, RVFpRegister>(
+      dest_op_getters_, this);
+  AddRiscV32SimpleResourceGetters<SimpleResourceEnum, Extractors>(
+      simple_resource_getters_, this);
+  // Add the common Zba operand getters.
+  AddRiscVZbaSourceGetters<SourceOpEnum, Extractors, RV32Register,
+                           RVFpRegister>(source_op_getters_, this);
+  // Add operand getters for the 32 bit Zbb instructions.
+  AddRiscVZbb32SourceGetters<SourceOpEnum, Extractors, RV32Register,
+                             RVFpRegister>(source_op_getters_, this);
+  // Add vector operand getters.
+  AddRiscVVectorSourceGetters<SourceOpEnum, Extractors, RVVectorRegister>(
+      source_op_getters_, this);
+  AddRiscVVectorDestGetters<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;
+    }
+  }
+}
+
+RiscV32GZBVecEncoding::~RiscV32GZBVecEncoding() { delete resource_pool_; }
+
+void RiscV32GZBVecEncoding::ParseInstruction(uint32_t inst_word) {
+  inst_word_ = inst_word;
+  if ((inst_word_ & 0x3) == 3) {
+    auto [opcode, format] = DecodeRiscV32GVZBWithFormat(inst_word_);
+    opcode_ = opcode;
+    format_ = format;
+    return;
+  }
+  auto [opcode, format] =
+      DecodeRiscVCInst16WithFormat(static_cast<uint16_t>(inst_word & 0xffff));
+  opcode_ = opcode;
+  format_ = format;
+}
+
+ResourceOperandInterface *RiscV32GZBVecEncoding::GetComplexResourceOperand(
+    SlotEnum, int, OpcodeEnum, ComplexResourceEnum resource, int begin,
+    int end) {
+  int index = static_cast<int>(resource);
+  auto iter = complex_resource_getters_.find(index);
+  if (iter == complex_resource_getters_.end()) {
+    LOG(WARNING) << "No complex resource getter found for resource: " << index;
+    return nullptr;
+  }
+  return (iter->second)(begin, end);
+}
+
+ResourceOperandInterface *RiscV32GZBVecEncoding::GetSimpleResourceOperand(
+    SlotEnum, int, OpcodeEnum, SimpleResourceVector &resource_vec, int end) {
+  if (resource_vec.empty()) return nullptr;
+  auto *resource_set = resource_pool_->CreateResourceSet();
+  for (auto resource_enum : resource_vec) {
+    int index = static_cast<int>(resource_enum);
+    auto iter = simple_resource_getters_.find(index);
+    if (iter == simple_resource_getters_.end()) {
+      LOG(WARNING) << "No getter for simple resource " << index;
+      continue;
+    }
+    auto *resource = (iter->second)();
+    auto status = resource_set->AddResource(resource);
+    if (!status.ok()) {
+      LOG(ERROR) << "Unable to add resource to resource set ("
+                 << static_cast<int>(resource_enum) << ")";
+    }
+  }
+  auto *op = new generic::SimpleResourceOperand(resource_set, end,
+                                                resource_delay_line_);
+  return op;
+}
+
+DestinationOperandInterface *RiscV32GZBVecEncoding::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 *RiscV32GZBVecEncoding::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::isa32gvzb
diff --git a/riscv/riscv32gzb_vec_encoding.h b/riscv/riscv32gzb_vec_encoding.h
new file mode 100644
index 0000000..7b2448d
--- /dev/null
+++ b/riscv/riscv32gzb_vec_encoding.h
@@ -0,0 +1,95 @@
+// 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_RISCV32GZB_VEC_ENCODING_H_
+#define THIRD_PARTY_MPACT_RISCV_RISCV32GZB_VEC_ENCODING_H_
+
+#include <cstdint>
+#include <string>
+
+#include "mpact/sim/generic/simple_resource.h"
+#include "mpact/sim/generic/simple_resource_operand.h"
+#include "riscv/riscv32gvzb_bin_decoder.h"
+#include "riscv/riscv32gvzb_decoder.h"
+#include "riscv/riscv32gvzb_enums.h"
+#include "riscv/riscv_encoding_common.h"
+#include "riscv/riscv_getter_helpers.h"
+#include "riscv/riscv_state.h"
+
+namespace mpact::sim::riscv::isa32gvzb {
+
+// 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 RiscV32GZBVecEncoding : public RiscV32GVZBEncodingBase,
+                              public RiscVEncodingCommon {
+ public:
+  explicit RiscV32GZBVecEncoding(RiscVState *state);
+  ~RiscV32GZBVecEncoding() 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_; }
+
+ 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::isa32gvzb
+
+#endif  // THIRD_PARTY_MPACT_RISCV_RISCV32GZB_VEC_ENCODING_H_
diff --git a/riscv/riscv32v.bin_fmt b/riscv/riscv32v.bin_fmt
index 8684406..856c33c 100644
--- a/riscv/riscv32v.bin_fmt
+++ b/riscv/riscv32v.bin_fmt
@@ -25,5 +25,18 @@
   RiscVCInst16;
 };
 
+decoder RiscV32GVZB {
+  namespace mpact::sim::riscv::isa32gvzb;
+  opcode_enum = "isa32gvzb::OpcodeEnum";
+  includes {
+    #include "riscv/riscv32gvzb_decoder.h"
+  }
+  // Group these instruction groups in the same decoder function.
+  RiscV32GVZB = {RiscVGInst32, RiscVVInst32, RiscVZbaInst32, RiscVZbbInst32,
+                 RiscVZbcInst32, RiscVZbsInst32};
+  // Keep this separate (different base format).
+  RiscVCInst16;
+}
+#include "riscv/riscv32zb.bin_fmt"
 #include "riscv/riscv32g.bin_fmt"
 #include "riscv/riscv_vector.bin_fmt"
diff --git a/riscv/riscv32v.isa b/riscv/riscv32v.isa
index 27563e4..60ab378 100644
--- a/riscv/riscv32v.isa
+++ b/riscv/riscv32v.isa
@@ -24,7 +24,12 @@
   slots { riscv32gv; }
 }
 
-#include "riscv/riscv32g.isa"
+isa RiscV32GVZB {
+  namespace mpact::sim::riscv::isa32gvzb;
+  slots { riscv32gvzb; }
+}
+
+#include "riscv/riscv32gzb.isa"
 #include "riscv/riscv_vector.isa"
 
 slot riscv32gv : riscv32g, riscv_vector {
@@ -32,4 +37,11 @@
   default opcode =
     disasm: "Illegal instruction at 0x%(@:08x)",
     semfunc: "&RiscVIllegalInstruction";
+}
+
+slot riscv32gvzb : riscv32gzb, riscv_vector {
+  default size = 4;
+  default opcode =
+    disasm: "Illegal instruction at 0x%(@:08x)",
+    semfunc: "&RiscVIllegalInstruction";
 }
\ No newline at end of file
diff --git a/riscv/riscv64gzb_vec_encoding.cc b/riscv/riscv64gzb_vec_encoding.cc
index b0c4c8b..43e913a 100644
--- a/riscv/riscv64gzb_vec_encoding.cc
+++ b/riscv/riscv64gzb_vec_encoding.cc
@@ -15,7 +15,6 @@
 #include "riscv/riscv64gzb_vec_encoding.h"
 
 #include <cstdint>
-#include <new>
 
 #include "absl/log/log.h"
 #include "absl/strings/str_cat.h"
diff --git a/riscv/riscv_bitmanip_instructions.cc b/riscv/riscv_bitmanip_instructions.cc
index 0084402..dda17c2 100644
--- a/riscv/riscv_bitmanip_instructions.cc
+++ b/riscv/riscv_bitmanip_instructions.cc
@@ -491,7 +491,7 @@
   RiscVBinaryOp<RegisterType, UIntReg, UIntReg>(
       instruction, [](UIntReg a, UIntReg b) {
         UIntReg result = 0;
-        for (int i = 0; i < kXlen - 1; i++) {
+        for (int i = 0; i < kXlen; i++) {
           result = ((b >> i) & 1) ? result ^ (a >> (kXlen - i - 1)) : result;
         }
         return result;
diff --git a/riscv/riscv_csr.h b/riscv/riscv_csr.h
index 54d5207..6765b2c 100644
--- a/riscv/riscv_csr.h
+++ b/riscv/riscv_csr.h
@@ -83,7 +83,8 @@
   kSIDeleg = 0x103,
   kSIe = 0x104,
   kSTvec = 0x105,
-  kSCounteren = 0x106,
+  kSCounteren = 0x106,  // Supervisor counter enable.
+  kSenvcfg = 0x10a,     // Supervisor environment configuration register.
   // Supervisor trap handling.
   kSScratch = 0x140,  // Scratch register for supervisor trap handlers.
   kSEpc = 0x141,      // Supervisor exception program counter.
@@ -113,6 +114,7 @@
   kMCause = 0x342,    // Machine trap cause.
   kMTval = 0x343,     // Machine bad address or instruction.
   kMIp = 0x344,       // Machine interrupt pending.
+  kMenvcfg = 0x30A,   // Machine environment configuration register.
 
   // PMP registers.
   kPmpCfg0 = 0x3a0,    // PMP configuration register 0.
@@ -136,6 +138,9 @@
   kPmpAddr14 = 0x3be,  // PMP address register 14.
   kPmpAddr15 = 0x3bf,  // PMP address register 15.
 
+  // Hypervisor level CSRs.
+  kHenvcfg = 0x60A,  // Hypervisor environment configuration register.
+
   // Ignoring machine memory protection for now.
 
   kMCycle = 0xb00,    // Machine cycle counter.
diff --git a/riscv/riscv_f_instructions.cc b/riscv/riscv_f_instructions.cc
index 1864bac..34b572a 100644
--- a/riscv/riscv_f_instructions.cc
+++ b/riscv/riscv_f_instructions.cc
@@ -221,7 +221,7 @@
 
 // If either operand is NaN return the other.
 void RiscVFMin(const Instruction *instruction) {
-  RiscVBinaryOp<FPRegister, float, float>(
+  RiscVBinaryNaNBoxOp<FPRegister::ValueType, float, float>(
       instruction, [instruction](float a, float b) -> float {
         if (FPTypeInfo<float>::IsSNaN(a) || FPTypeInfo<float>::IsSNaN(b)) {
           auto *db = instruction->Destination(1)->AllocateDataBuffer();
@@ -236,7 +236,9 @@
           }
           return b;
         }
-        if (FPTypeInfo<float>::IsNaN(b)) return a;
+        if (FPTypeInfo<float>::IsNaN(b)) {
+          return a;
+        }
         // If both are zero, return the negative zero if there is one.
         if ((a == 0.0) && (b == 0.0)) return (std::signbit(a)) ? a : b;
         return (a > b) ? b : a;
@@ -245,7 +247,7 @@
 
 // If either operand is NaN return the other.
 void RiscVFMax(const Instruction *instruction) {
-  RiscVBinaryOp<FPRegister, float, float>(
+  RiscVBinaryNaNBoxOp<FPRegister::ValueType, float, float>(
       instruction, [instruction](float a, float b) -> float {
         if (FPTypeInfo<float>::IsSNaN(a) || FPTypeInfo<float>::IsSNaN(b)) {
           auto *db = instruction->Destination(1)->AllocateDataBuffer();
diff --git a/riscv/riscv_getters.h b/riscv/riscv_getters.h
index 5230372..4aee8bb 100644
--- a/riscv/riscv_getters.h
+++ b/riscv/riscv_getters.h
@@ -76,7 +76,7 @@
     auto num = Extractors::CS::ExtractCsRs2(common->inst_word());
     return GetRegisterSourceOp<FpRegister>(
         common->state(), absl::StrCat(RiscVState::kFregPrefix, num),
-        kXRegisterAliases[num]);
+        kFRegisterAliases[num]);
   });
   Insert(getter_map, *Enum::kC3rs1, [common]() {
     auto num = Extractors::CS::ExtractCsRs1(common->inst_word());
@@ -98,7 +98,7 @@
     auto num = Extractors::CR::ExtractRs2(common->inst_word());
     return GetRegisterSourceOp<FpRegister>(
         common->state(), absl::StrCat(RiscVState::kFregPrefix, num),
-        kXRegisterAliases[num]);
+        kFRegisterAliases[num]);
   });
   Insert(getter_map, *Enum::kCrs1, [common]() {
     auto num = Extractors::CR::ExtractRs1(common->inst_word());
@@ -125,37 +125,37 @@
     int num = Extractors::RType::ExtractRs1(common->inst_word());
     return GetRegisterSourceOp<IntRegister>(
         common->state(), absl::StrCat(RiscVState::kFregPrefix, num),
-        kXRegisterAliases[num]);
+        kFRegisterAliases[num]);
   });
   Insert(getter_map, *Enum::kDrs2, [common]() -> SourceOperandInterface * {
     int num = Extractors::RType::ExtractRs2(common->inst_word());
     return GetRegisterSourceOp<IntRegister>(
         common->state(), absl::StrCat(RiscVState::kFregPrefix, num),
-        kXRegisterAliases[num]);
+        kFRegisterAliases[num]);
   });
   Insert(getter_map, *Enum::kDrs3, [common]() -> SourceOperandInterface * {
     int num = Extractors::R4Type::ExtractRs3(common->inst_word());
     return GetRegisterSourceOp<IntRegister>(
         common->state(), absl::StrCat(RiscVState::kFregPrefix, num),
-        kXRegisterAliases[num]);
+        kFRegisterAliases[num]);
   });
   Insert(getter_map, *Enum::kFrs1, [common]() -> SourceOperandInterface * {
     int num = Extractors::RType::ExtractRs1(common->inst_word());
     return GetRegisterSourceOp<IntRegister>(
         common->state(), absl::StrCat(RiscVState::kFregPrefix, num),
-        kXRegisterAliases[num]);
+        kFRegisterAliases[num]);
   });
   Insert(getter_map, *Enum::kFrs2, [common]() -> SourceOperandInterface * {
     int num = Extractors::RType::ExtractRs2(common->inst_word());
     return GetRegisterSourceOp<IntRegister>(
         common->state(), absl::StrCat(RiscVState::kFregPrefix, num),
-        kXRegisterAliases[num]);
+        kFRegisterAliases[num]);
   });
   Insert(getter_map, *Enum::kFrs3, [common]() -> SourceOperandInterface * {
     int num = Extractors::R4Type::ExtractRs3(common->inst_word());
     return GetRegisterSourceOp<IntRegister>(
         common->state(), absl::StrCat(RiscVState::kFregPrefix, num),
-        kXRegisterAliases[num]);
+        kFRegisterAliases[num]);
   });
   Insert(getter_map, *Enum::kICbImm8, [common]() {
     return new ImmediateOperand<int32_t>(
@@ -307,7 +307,8 @@
   Insert(getter_map, *Enum::kC3drd, [common](int latency) {
     int num = Extractors::CL::ExtractClRd(common->inst_word());
     return GetRegisterDestinationOp<IntRegister>(
-        common->state(), absl::StrCat(RiscVState::kFregPrefix, num), latency);
+        common->state(), absl::StrCat(RiscVState::kFregPrefix, num), latency,
+        kFRegisterAliases[num]);
   });
   Insert(getter_map, *Enum::kC3rd, [common](int latency) {
     int num = Extractors::CL::ExtractClRd(common->inst_word());
@@ -330,14 +331,14 @@
            int num = Extractors::RType::ExtractRd(common->inst_word());
            return GetRegisterDestinationOp<IntRegister>(
                common->state(), absl::StrCat(RiscVState::kFregPrefix, num),
-               latency);
+               latency, kFRegisterAliases[num]);
          });
   Insert(getter_map, *Enum::kFrd,
          [common](int latency) -> DestinationOperandInterface * {
            int num = Extractors::RType::ExtractRd(common->inst_word());
            return GetRegisterDestinationOp<IntRegister>(
                common->state(), absl::StrCat(RiscVState::kFregPrefix, num),
-               latency);
+               latency, kFRegisterAliases[num]);
          });
   Insert(getter_map, *Enum::kNextPc,
          [common](int latency) -> DestinationOperandInterface * {
diff --git a/riscv/riscv_i_instructions.cc b/riscv/riscv_i_instructions.cc
index a72fe68..eb17c54 100644
--- a/riscv/riscv_i_instructions.cc
+++ b/riscv/riscv_i_instructions.cc
@@ -128,6 +128,8 @@
   UIntReg return_address = instruction->address() + instruction->size();
   auto *db = instruction->Destination(0)->AllocateDataBuffer();
   db->SetSubmit<UIntReg>(0, target);
+  auto *state = static_cast<RiscVState *>(instruction->state());
+  state->set_branch(true);
   auto *reg = static_cast<generic::RegisterDestinationOperand<UIntReg> *>(
                   instruction->Destination(1))
                   ->GetRegister();
@@ -141,6 +143,8 @@
   UIntReg return_address = instruction->address() + instruction->size();
   auto *db = instruction->Destination(0)->AllocateDataBuffer();
   db->SetSubmit<UIntReg>(0, target);
+  auto *state = static_cast<RiscVState *>(instruction->state());
+  state->set_branch(true);
   auto *reg = static_cast<generic::RegisterDestinationOperand<UIntReg> *>(
                   instruction->Destination(1))
                   ->GetRegister();
@@ -277,6 +281,8 @@
   UIntReg return_address = instruction->address() + instruction->size();
   auto *db = instruction->Destination(0)->AllocateDataBuffer();
   db->SetSubmit<UIntReg>(0, target);
+  auto *state = static_cast<RiscVState *>(instruction->state());
+  state->set_branch(true);
   auto *reg = static_cast<generic::RegisterDestinationOperand<UIntReg> *>(
                   instruction->Destination(1))
                   ->GetRegister();
@@ -291,6 +297,8 @@
   UIntReg return_address = instruction->address() + instruction->size();
   auto *db = instruction->Destination(0)->AllocateDataBuffer();
   db->SetSubmit<UIntReg>(0, target);
+  auto *state = static_cast<RiscVState *>(instruction->state());
+  state->set_branch(true);
   auto *reg = static_cast<generic::RegisterDestinationOperand<UIntReg> *>(
                   instruction->Destination(1))
                   ->GetRegister();
diff --git a/riscv/riscv_instruction_helpers.h b/riscv/riscv_instruction_helpers.h
index db40dc9..09a4e03 100644
--- a/riscv/riscv_instruction_helpers.h
+++ b/riscv/riscv_instruction_helpers.h
@@ -281,10 +281,8 @@
   if (sizeof(RegValue) <= sizeof(Argument)) {
     return generic::GetInstructionSource<Argument>(instruction, arg);
   } else {
-    using SInt = typename std::make_signed<RegValue>::type;
     using UInt = typename std::make_unsigned<RegValue>::type;
-    SInt val = generic::GetInstructionSource<SInt>(instruction, arg);
-    UInt uval = static_cast<UInt>(val);
+    UInt uval = generic::GetInstructionSource<UInt>(instruction, arg);
     UInt mask = std::numeric_limits<UInt>::max() << (sizeof(Argument) * 8);
     if (((mask & uval) != mask)) {
       return *reinterpret_cast<const Argument *>(
@@ -346,6 +344,8 @@
     UIntType target = offset + instruction->address();
     auto *db = instruction->Destination(0)->AllocateDataBuffer();
     db->SetSubmit<UIntType>(0, target);
+    auto state = static_cast<RiscVState *>(instruction->state());
+    state->set_branch(true);
   }
 }
 
@@ -583,8 +583,6 @@
     std::function<Result(Argument, Argument)> operation) {
   Argument lhs = GetNaNBoxedSource<Register, Argument>(instruction, 0);
   Argument rhs = GetNaNBoxedSource<Register, Argument>(instruction, 1);
-  // Argument lhs = generic::GetInstructionSource<Argument>(instruction, 0);
-  // Argument rhs = generic::GetInstructionSource<Argument>(instruction, 1);
 
   // Get the rounding mode.
   int rm_value = generic::GetInstructionSource<int>(instruction, 2);
diff --git a/riscv/riscv_priv_instructions.cc b/riscv/riscv_priv_instructions.cc
index 3bd7658..fd23133 100644
--- a/riscv/riscv_priv_instructions.cc
+++ b/riscv/riscv_priv_instructions.cc
@@ -65,6 +65,7 @@
   auto *db = inst->Destination(0)->AllocateDataBuffer();
   // Write the contents of mepc to the pc.
   db->SetSubmit<UIntReg>(0, sepc->AsUint32());
+  state->set_branch(true);
   // Need to update new privilege level.
   res = state->csr_set()->GetCsr(*RiscVCsrEnum::kMStatus);
   if (!res.ok()) {
@@ -114,6 +115,7 @@
   auto *db = inst->Destination(0)->AllocateDataBuffer();
   // Write the contents of mepc to the pc.
   db->SetSubmit<UIntReg>(0, mepc->AsUint32());
+  state->set_branch(true);
   // Need to update new privilege level.
   res = state->csr_set()->GetCsr(*RiscVCsrEnum::kMStatus);
   if (!res.ok()) {
@@ -164,8 +166,10 @@
 
 void RiscVPrivSRet(const Instruction *inst) {
   RiscVState *state = static_cast<RiscVState *>(inst->state());
-  if (state->privilege_mode() != PrivilegeMode::kSupervisor) {
-    LOG(ERROR) << "sret executed when not in Supervisor mode";
+  if (*state->privilege_mode() < *PrivilegeMode::kSupervisor) {
+    LOG(ERROR) << absl::StrCat(
+        "sret executed when not in Supervisor mode at pc = 0x",
+        absl::Hex(inst->address()));
     state->Trap(/*is_interrupt*/ false, /*trap_value*/ 0,
                 *ExceptionCode::kIllegalInstruction, inst->address(), inst);
     return;
@@ -186,6 +190,7 @@
   auto *db = inst->Destination(0)->AllocateDataBuffer();
   // Write the contents of mepc to the pc.
   db->SetSubmit<UIntReg>(0, sepc->AsUint64());
+  state->set_branch(true);
   // Need to update new privilege level.
   res = state->csr_set()->GetCsr(*RiscVCsrEnum::kMStatus);
   if (!res.ok()) {
@@ -211,7 +216,19 @@
   mstatus->set_sie(mstatus->spie());
   // Set mstatus:mpie to 1.
   mstatus->set_spie(1);
-  mstatus->set_spp(*PrivilegeMode::kUser & 0b1);
+  // Get misa too.
+  res = state->csr_set()->GetCsr(*RiscVCsrEnum::kMIsa);
+  if (!res.ok()) {
+    LOG(ERROR) << absl::StrCat("At PC=", absl::Hex(inst->address()),
+                               " mret: cannot access isa");
+    return;
+  }
+  auto *misa = static_cast<RiscVMIsa *>(*res);
+  if (misa->HasUserMode()) {
+    mstatus->set_spp(*PrivilegeMode::kUser);
+  } else {
+    mstatus->set_spp(*PrivilegeMode::kMachine);
+  }
   state->set_privilege_mode(static_cast<PrivilegeMode>(target_mode));
   state->SignalReturnFromInterrupt();
   mstatus->Submit();
@@ -235,6 +252,7 @@
   auto *db = inst->Destination(0)->AllocateDataBuffer();
   // Write the contents of mepc to the pc.
   db->SetSubmit<UIntReg>(0, mepc->AsUint64());
+  state->set_branch(true);
   // Need to update new privilege level.
   res = state->csr_set()->GetCsr(*RiscVCsrEnum::kMStatus);
   if (!res.ok()) {
diff --git a/riscv/riscv_state.cc b/riscv/riscv_state.cc
index 7eebd76..8d71bad 100644
--- a/riscv/riscv_state.cc
+++ b/riscv/riscv_state.cc
@@ -162,6 +162,12 @@
 void CreateCsrs(RiscVState *state, std::vector<RiscVCsrInterface *> &csr_vec) {
   absl::Status result;
   // Create CSRs.
+
+  // menvcfg
+  CHECK_NE(CreateCsr<RiscVSimpleCsr<T>>(state, csr_vec, "menvcfg",
+                                        RiscVCsrEnum::kMenvcfg, 0, state),
+           nullptr);
+
   // misa
   auto *misa = CreateCsr(state, state->misa_, csr_vec,
                          CsrInfo<T>::kMisaInitialValue, state);
@@ -247,8 +253,25 @@
         nullptr);
   }
 
+  // Hypervisor level CSRs
+
+  // henvcfg
+  CHECK_NE(CreateCsr<RiscVSimpleCsr<T>>(state, csr_vec, "henvcfg",
+                                        RiscVCsrEnum::kHenvcfg, 0, state),
+           nullptr);
+
   // Supervisor level CSRs
 
+  // senvcfg
+  CHECK_NE(CreateCsr<RiscVSimpleCsr<T>>(state, csr_vec, "senvcfg",
+                                        RiscVCsrEnum::kSenvcfg, 0, state),
+           nullptr);
+
+  // scounteren
+  CHECK_NE(CreateCsr<RiscVSimpleCsr<T>>(state, csr_vec, "scounteren",
+                                        RiscVCsrEnum::kSCounteren, 0, state),
+           nullptr);
+
   // sstatus
   CHECK_NE(CreateCsr<RiscVSStatus>(state, csr_vec, mstatus, state), nullptr);
 
@@ -530,13 +553,15 @@
     if (res) return;
   }
 
-  // If no handler is specidied, then CEASE is treated as an infinite loop.
+  // If no handler is specified, then CEASE is treated as an infinite loop.
   auto current_xlen = xlen();
   auto *db = pc_dst_operand_->AllocateDataBuffer();
   if (current_xlen == RiscVXlen::RV32) {
     db->SetSubmit<uint32_t>(0, static_cast<uint32_t>(inst->address()));
+    set_branch(true);
   } else if (current_xlen == RiscVXlen::RV64) {
     db->SetSubmit<uint64_t>(0, inst->address());
+    set_branch(true);
   } else {
     LOG(ERROR) << "Unknown xlen";
   }
@@ -581,7 +606,6 @@
       }
     }
   } else {
-    // Exceptions are not delegated to a lower privilege level.
     if ((privilege_mode() != PrivilegeMode::kMachine) &&
         (medeleg_->AsUint64() & (1ULL << exception_code))) {
       destination_mode = PrivilegeMode::kSupervisor;
@@ -656,8 +680,10 @@
   auto *db = pc_dst_operand_->AllocateDataBuffer();
   if (current_xlen == RiscVXlen::RV32) {
     db->SetSubmit<uint32_t>(0, static_cast<uint32_t>(trap_target));
+    set_branch(true);
   } else if (current_xlen == RiscVXlen::RV64) {
     db->SetSubmit<uint64_t>(0, trap_target);
+    set_branch(true);
   } else {
     LOG(ERROR) << "Unknown xlen";
   }
diff --git a/riscv/riscv_state.h b/riscv/riscv_state.h
index 97aa718..3c4bbd2 100644
--- a/riscv/riscv_state.h
+++ b/riscv/riscv_state.h
@@ -372,6 +372,9 @@
     is_interrupt_available_ = false;
   }
 
+  void set_branch(bool value) { branch_ = value; }
+  bool branch() const { return branch_; }
+
   // Getters for select CSRs.
   RiscVMStatus *mstatus() const { return mstatus_; }
   RiscVMIsa *misa() const { return misa_; }
@@ -421,6 +424,8 @@
   InterruptCode available_interrupt_code_ = InterruptCode::kNone;
   // By default, execute in machine mode.
   PrivilegeMode privilege_mode_ = PrivilegeMode::kMachine;
+  // Flag set on branch instructions.
+  bool branch_ = false;
   // Handles to frequently used CSRs.
   RiscVMStatus *mstatus_ = nullptr;
   RiscVMIsa *misa_ = nullptr;
diff --git a/riscv/riscv_top.cc b/riscv/riscv_top.cc
index d0e0b41..17885c7 100644
--- a/riscv/riscv_top.cc
+++ b/riscv/riscv_top.cc
@@ -25,6 +25,7 @@
 #include "absl/functional/bind_front.h"
 #include "absl/log/check.h"
 #include "absl/log/log.h"
+#include "absl/numeric/bits.h"
 #include "absl/status/status.h"
 #include "absl/strings/str_cat.h"
 #include "absl/strings/str_format.h"
@@ -107,6 +108,8 @@
     run_halted_ = nullptr;
   }
 
+  if (branch_trace_db_ != nullptr) branch_trace_db_->DecRef();
+
   delete icache_;
   delete dcache_;
   if (inst_db_) inst_db_->DecRef();
@@ -199,6 +202,13 @@
     return false;
   });
   inst_db_ = db_factory_.Allocate<uint32_t>(1);
+  // Branch trace.
+  branch_trace_db_ = db_factory_.Allocate<BranchTraceEntry>(kBranchTraceSize);
+  branch_trace_ =
+      reinterpret_cast<BranchTraceEntry *>(branch_trace_db_->raw_ptr());
+  for (int i = 0; i < kBranchTraceSize; i++) {
+    branch_trace_[i] = {0, 0, 0};
+  }
 }
 
 void RiscVTop::ConfigureCache(Cache *&cache, Config<std::string> &config) {
@@ -250,8 +260,7 @@
   // Execute the real instruction.
   auto real_inst = rv_decode_cache_->GetDecodedInstruction(pc);
   real_inst->IncRef();
-  uint64_t next_seq_pc = pc + real_inst->size();
-  SetPc(next_seq_pc);
+  uint64_t next_pc = pc + real_inst->size();
   bool executed = false;
   if (icache_) ICacheFetch(pc);
   do {
@@ -266,6 +275,13 @@
   // Re-enable the breakpoint.
   (void)rv_action_point_manager_->ap_memory_interface()
       ->WriteBreakpointInstruction(bpt_pc);
+  if (state_->branch()) {
+    state_->set_branch(false);
+    auto new_pc = state_->pc_operand()->AsUint64(0);
+    AddToBranchTrace(pc, bpt_pc);
+    next_pc = new_pc;
+  }
+  SetPc(next_pc);
   return absl::OkStatus();
 }
 
@@ -303,13 +319,12 @@
   // executed next. Post-loop it holds the address of the next instruction to
   // be executed.
   uint64_t next_pc = pc_operand->AsUint64(0);
-  while (count < num) {
-    pc = next_pc;
+  pc = next_pc;
+  while (!halted_ && (count < num)) {
     SetPc(pc);
     auto *inst = rv_decode_cache_->GetDecodedInstruction(pc);
-    // Set the PC destination operand to the next sequential address. Any branch
-    // that is executed will overwrite this.
-    SetPc(pc + inst->size());
+    // Set the next_pc to the next sequential instruction.
+    next_pc = pc + inst->size();
     bool executed = false;
     if (icache_) ICacheFetch(pc);
     do {
@@ -318,7 +333,7 @@
       state_->AdvanceDelayLines();
       // Check for interrupt.
       if (state_->is_interrupt_available()) {
-        uint64_t epc = (executed ? state_->pc_operand()->AsUint64(0) : pc);
+        uint64_t epc = (executed ? state_->pc_operand()->AsUint64(0) : next_pc);
         state_->TakeAvailableInterrupt(epc);
       }
     } while (!executed);
@@ -327,8 +342,16 @@
     counter_opcode_[inst->opcode()].Increment(1);
     counter_num_instructions_.Increment(1);
     // Get the next pc value.
-    next_pc = state_->pc_operand()->AsUint64(0);
-    if (!halted_) continue;
+    auto pc_val = state_->pc_operand()->AsUint64(0);
+    if (state_->branch()) {
+      state_->set_branch(false);
+      AddToBranchTrace(pc, pc_val);
+      next_pc = pc_val;
+    }
+    if (!halted_) {
+      pc = next_pc;
+      continue;
+    }
     // If it's an action point, just step over and continue.
     if (halt_reason_ == *HaltReason::kActionPoint) {
       auto status = StepPastBreakpoint();
@@ -373,30 +396,29 @@
     auto status = StepPastBreakpoint();
     if (!status.ok()) return status;
   }
-  run_status_ = RunStatus::kRunning;
-  halted_ = false;
-  halt_reason_ = *HaltReason::kNone;
-
   // The simulator is now run in a separate thread so as to allow a user
   // interface to continue operating. Allocate a new run_halted_ Notification
   // object, as they are single use only.
   run_halted_ = new absl::Notification();
+  run_started_ = new absl::Notification();
   // The thread is detached so it executes without having to be joined.
   std::thread([this]() {
+    run_status_ = RunStatus::kRunning;
+    halted_ = false;
+    halt_reason_ = *HaltReason::kNone;
+    run_started_->Notify();
     auto pc_operand = state_->pc_operand();
-    // This holds the value of the current pc, and post-loop, the address of
-    // the most recently executed instruction.
-    uint64_t pc;
     // At the top of the loop this holds the address of the instruction to be
     // executed next. Post-loop it holds the address of the next instruction to
     // be executed.
     uint64_t next_pc = pc_operand->AsUint64(0);
-    while (true) {
-      pc = next_pc;
+    // This holds the value of the current pc, and post-loop, the address of
+    // the most recently executed instruction.
+    uint64_t pc = next_pc;
+    while (!halted_) {
       auto *inst = rv_decode_cache_->GetDecodedInstruction(pc);
-      // Set the PC destination operand to next_seq_pc. Any branch that is
-      // executed will overwrite this.
-      SetPc(pc + inst->size());
+      SetPc(pc);
+      next_pc = pc + inst->size();
       bool executed = false;
       if (icache_) ICacheFetch(pc);
       do {
@@ -415,8 +437,16 @@
       counter_opcode_[inst->opcode()].Increment(1);
       counter_num_instructions_.Increment(1);
       // Get the next pc value.
-      next_pc = pc_operand->AsUint64(0);
-      if (!halted_) continue;
+      uint64_t pc_val = pc_operand->AsUint64(0);
+      if (state_->branch()) {
+        state_->set_branch(false);
+        AddToBranchTrace(pc, pc_val);
+        next_pc = pc_val;
+      }
+      if (!halted_) {
+        pc = next_pc;
+        continue;
+      }
       // If it's an action point, just step over and continue executing, as
       // this is not a full breakpoint.
       if (halt_reason_ == *HaltReason::kActionPoint) {
@@ -445,6 +475,9 @@
     // Notify that the run has completed.
     run_halted_->Notify();
   }).detach();
+  run_started_->WaitForNotification();
+  delete run_started_;
+  run_started_ = nullptr;
   return absl::OkStatus();
 }
 
@@ -480,6 +513,9 @@
   if (iter == state_->registers()->end()) {
     auto result = state_->csr_set()->GetCsr(name);
     if (!result.ok()) {
+      // See if it is $branch_trace_head.
+      if (name == "$branch_trace_head") return branch_trace_head_;
+      if (name == "$branch_trace_size") return branch_trace_size_;
       return absl::NotFoundError(
           absl::StrCat("Register '", name, "' not found"));
     }
@@ -527,6 +563,9 @@
   if (iter == state_->registers()->end()) {
     auto result = state_->csr_set()->GetCsr(name);
     if (!result.ok()) {
+      if (name == "$branch_trace_size") {
+        return ResizeBranchTrace(value);
+      }
       return absl::NotFoundError(
           absl::StrCat("Register '", name, "' not found"));
     }
@@ -578,6 +617,7 @@
     return absl::FailedPreconditionError(
         "GetRegisterDataBuffer: Core must be halted");
   }
+  if (name == "$branch_trace") return branch_trace_db_;
   auto iter = state_->registers()->find(name);
   if (iter == state_->registers()->end()) {
     return absl::NotFoundError(absl::StrCat("Register '", name, "' not found"));
@@ -803,6 +843,57 @@
   }
 }
 
+absl::Status RiscVTop::ResizeBranchTrace(size_t size) {
+  if (absl::popcount(size) != 1) {
+    return absl::InvalidArgumentError("Invalid size - must be a power of 2");
+  }
+  auto *new_db = db_factory_.Allocate<BranchTraceEntry>(size);
+  auto *new_trace = reinterpret_cast<BranchTraceEntry *>(new_db->raw_ptr());
+  if (new_db == nullptr) {
+    return absl::InternalError("Failed to allocate new branch trace buffer");
+  }
+  // Copy entries from the old buffer to the new buffer, but do it so that
+  // the most recent entry of the old buffer is at the end of the newly
+  // allocated buffer. That way, if the new buffer is smaller, we don't have to
+  // do too much special handling.
+  int new_index = size - 1;
+  int old_index = branch_trace_head_;
+  while ((new_index >= 0) && (branch_trace_[old_index].count > 0)) {
+    new_trace[new_index] = branch_trace_[old_index];
+    new_index--;
+    old_index--;
+    if (old_index < 0) {
+      old_index = branch_trace_size_ - 1;
+    }
+    // Stop if we get to the beginning of the old trace.
+    if (old_index == branch_trace_head_) break;
+  }
+  while (new_index >= 0) {
+    new_trace[new_index] = {0, 0, 0};
+    new_index--;
+  }
+  branch_trace_db_->DecRef();
+  branch_trace_db_ = new_db;
+  branch_trace_ = new_trace;
+  branch_trace_size_ = size;
+  branch_trace_mask_ = branch_trace_size_ - 1;
+  branch_trace_head_ = branch_trace_mask_;
+  return absl::OkStatus();
+}
+
+void RiscVTop::AddToBranchTrace(uint64_t from, uint64_t to) {
+  // Get the most recent entry.
+  auto &entry = branch_trace_[branch_trace_head_];
+  // If the branch is the same as the previous, just increment its count.
+  if ((from == entry.from) && (to == entry.to)) {
+    entry.count++;
+    return;
+  }
+  branch_trace_head_ = (branch_trace_head_ + 1) & branch_trace_mask_;
+  branch_trace_[branch_trace_head_] = {static_cast<uint32_t>(from),
+                                       static_cast<uint32_t>(to), 1};
+}
+
 void RiscVTop::EnableStatistics() {
   for (auto &[unused, counter_ptr] : counter_map()) {
     if (counter_ptr->GetName() == "pc") continue;
diff --git a/riscv/riscv_top.h b/riscv/riscv_top.h
index 3d97f7b..7786ff2 100644
--- a/riscv/riscv_top.h
+++ b/riscv/riscv_top.h
@@ -54,11 +54,18 @@
 using ::mpact::sim::generic::Config;
 using ::mpact::sim::util::Cache;
 
-// Top level class for the RiscV32G simulator. This is the main interface for
+struct BranchTraceEntry {
+  uint32_t from;
+  uint32_t to;
+  uint32_t count;
+};
+
+// Top level class for the RiscV simulator. This is the main interface for
 // interacting and controlling execution of programs running on the simulator.
 // This class brings together the decoder, the architecture state, and control.
 class RiscVTop : public generic::Component, public RiscVDebugInterface {
  public:
+  static constexpr int kBranchTraceSize = 16;
   using RunStatus = generic::CoreDebugInterface::RunStatus;
   using HaltReason = generic::CoreDebugInterface::HaltReason;
 
@@ -157,6 +164,8 @@
   // Set the pc value.
   void SetPc(uint64_t value);
   void ICacheFetch(uint64_t address);
+  // Branch tracing.
+  void AddToBranchTrace(uint64_t from, uint64_t to);
 
   // The DB factory is used to manage data buffers for memory read/writes.
   generic::DataBufferFactory db_factory_;
@@ -168,6 +177,7 @@
   // Set to true if the next instruction requires a step-over.
   bool need_to_step_over_ = false;
   absl::Notification *run_halted_ = nullptr;
+  absl::Notification *run_started_ = nullptr;
   // The local RiscV32 state.
   RiscVState *state_;
   // Memory interface used by action point manager.
@@ -185,6 +195,17 @@
   // Decode cache, memory and memory watcher.
   generic::DecodeCache *rv_decode_cache_ = nullptr;
   util::MemoryWatcher *memory_watcher_ = nullptr;
+  // Branch trace info - uses a circular buffer. The size is defined by the
+  // constant kBranchTraceSize in the .cc file.
+  BranchTraceEntry *branch_trace_;
+  // Data buffer used to hold the branch trace info. This is used so that it
+  // can be returned to the debug command shell using the GetRegisterDataBuffer
+  // call.
+  DataBuffer *branch_trace_db_ = nullptr;
+  // Points to the most recently written entry in the circular buffer.
+  int branch_trace_head_ = 0;
+  int branch_trace_mask_ = kBranchTraceSize - 1;
+  int branch_trace_size_ = kBranchTraceSize;
   // Counter for the number of instructions simulated.
   std::vector<generic::SimpleCounter<uint64_t>> counter_opcode_;
   generic::SimpleCounter<uint64_t> counter_num_instructions_;
diff --git a/riscv/riscv_zc_instructions.cc b/riscv/riscv_zc_instructions.cc
index f7643cb..02ce209 100644
--- a/riscv/riscv_zc_instructions.cc
+++ b/riscv/riscv_zc_instructions.cc
@@ -107,6 +107,8 @@
   UIntReg target = generic::GetInstructionSource<UIntReg>(inst, 3);
   auto *db = inst->Destination(size + 1)->AllocateDataBuffer();
   db->SetSubmit<UIntReg>(0, target);
+  auto *state = static_cast<RiscVState *>(inst->state());
+  state->set_branch(true);
 }
 
 void RiscVZCmpPopRetz(const Instruction *inst) {
@@ -119,6 +121,8 @@
   UIntReg target = generic::GetInstructionSource<UIntReg>(inst, 3);
   auto *db = inst->Destination(size + 2)->AllocateDataBuffer();
   db->SetSubmit<UIntReg>(0, target);
+  auto *state = static_cast<RiscVState *>(inst->state());
+  state->set_branch(true);
 }
 
 void RiscVZCmpMvTwoRegs(const Instruction *inst) {
@@ -150,6 +154,7 @@
   // Write the target address to the next pc operand.
   auto *target_db = inst->Destination(0)->AllocateDataBuffer();
   target_db->SetSubmit<UIntReg>(0, target_address);
+  state->set_branch(true);
 }
 
 }  // namespace
@@ -243,6 +248,8 @@
   UIntReg target = generic::GetInstructionSource<UIntReg>(inst, 3);
   auto *db = inst->Destination(size + 1)->AllocateDataBuffer();
   db->SetSubmit<UIntReg>(0, target);
+  auto *state = static_cast<RiscVState *>(inst->state());
+  state->set_branch(true);
 }
 
 void RiscVZCmpPopRetz(const Instruction *inst) {
@@ -255,6 +262,8 @@
   UIntReg target = generic::GetInstructionSource<UIntReg>(inst, 3);
   auto *db = inst->Destination(size + 2)->AllocateDataBuffer();
   db->SetSubmit<UIntReg>(0, target);
+  auto *state = static_cast<RiscVState *>(inst->state());
+  state->set_branch(true);
 }
 
 void RiscVZCmpMvTwoRegs(const Instruction *inst) {
@@ -286,6 +295,7 @@
   // Write the target address to the next pc operand.
   auto *target_db = inst->Destination(0)->AllocateDataBuffer();
   target_db->SetSubmit<UIntReg>(0, target_address);
+  state->set_branch(true);
 }
 
 }  // namespace
diff --git a/riscv/rv32g_sim.cc b/riscv/rv32g_sim.cc
index 481fe3f..c46e944 100644
--- a/riscv/rv32g_sim.cc
+++ b/riscv/rv32g_sim.cc
@@ -26,9 +26,11 @@
 #include <string>
 #include <vector>
 
+#include "absl/base/log_severity.h"
 #include "absl/flags/flag.h"
 #include "absl/flags/parse.h"
 #include "absl/log/check.h"
+#include "absl/log/globals.h"
 #include "absl/log/log.h"
 #include "absl/strings/str_cat.h"
 #include "absl/strings/str_format.h"
@@ -42,6 +44,7 @@
 #include "mpact/sim/proto/component_data.pb.h"
 #include "mpact/sim/util/memory/atomic_memory.h"
 #include "mpact/sim/util/memory/flat_demand_memory.h"
+#include "mpact/sim/util/memory/memory_interface.h"
 #include "mpact/sim/util/memory/memory_watcher.h"
 #include "mpact/sim/util/program_loader/elf_program_loader.h"
 #include "re2/re2.h"
@@ -143,6 +146,12 @@
 // Enable bit manipulation instructions.
 ABSL_FLAG(bool, bitmanip, false, "Enable bit manipulation instructions");
 
+// Exit on write to 'tohost'
+ABSL_FLAG(bool, exit_on_tohost, false, "Exit on write to 'tohost'");
+
+// Quiet mode. Suppress informational and warning messages.
+ABSL_FLAG(bool, quiet, false, "Suppress informational and warning messages");
+
 // Flag to enable and configure the instruction and data caches.
 ABSL_FLAG(std::string, icache, "", "Instruction cache configuration");
 ABSL_FLAG(std::string, dcache, "", "Data cache configuration");
@@ -214,6 +223,7 @@
 }
 
 int main(int argc, char **argv) {
+  int return_code = 0;
   auto arg_vec = absl::ParseCommandLine(argc, argv);
 
   if (absl::GetFlag(FLAGS_semihost_htif) && absl::GetFlag(FLAGS_semihost_arm)) {
@@ -225,13 +235,26 @@
     std::cerr << "Only a single input file allowed" << std::endl;
     return -1;
   }
+
+  bool quiet = absl::GetFlag(FLAGS_quiet);
+  if (quiet) {
+    absl::SetMinLogLevel(absl::LogSeverityAtLeast::kError);
+  }
+
   std::string full_file_name = arg_vec[1];
   std::string file_name =
       full_file_name.substr(full_file_name.find_last_of('/') + 1);
   std::string file_basename = file_name.substr(0, file_name.find_first_of('.'));
 
   auto *memory = new mpact::sim::util::FlatDemandMemory();
-  auto *atomic_memory = new mpact::sim::util::AtomicMemory(memory);
+  mpact::sim::util::MemoryWatcher *memory_watcher = nullptr;
+  mpact::sim::util::AtomicMemory *atomic_memory = nullptr;
+  if (absl::GetFlag(FLAGS_exit_on_tohost)) {
+    memory_watcher = new mpact::sim::util::MemoryWatcher(memory);
+    atomic_memory = new mpact::sim::util::AtomicMemory(memory_watcher);
+  } else {
+    atomic_memory = new mpact::sim::util::AtomicMemory(memory);
+  }
   // Load the elf segments into memory.
   mpact::sim::util::ElfProgramLoader elf_loader(memory);
   auto load_result = elf_loader.LoadProgram(full_file_name);
@@ -241,8 +264,13 @@
     return -1;
   }
 
+  mpact::sim::util::MemoryInterface *memory_interface = memory;
+  if (memory_watcher != nullptr) {
+    memory_interface = memory_watcher;
+  }
   // Set up architectural state and decoder.
-  RiscVState rv_state("RiscV32", RiscVXlen::RV32, memory, atomic_memory);
+  RiscVState rv_state("RiscV32", RiscVXlen::RV32, memory_interface,
+                      atomic_memory);
   // For floating point support add the fp state.
   RiscVFPState rv_fp_state(rv_state.csr_set(), &rv_state);
   rv_state.set_rv_fp(&rv_fp_state);
@@ -299,6 +327,35 @@
       return true;
     });
   }
+  if (absl::GetFlag(FLAGS_exit_on_tohost)) {
+    auto res = elf_loader.GetSymbol("tohost");
+    if (res.ok()) {
+      auto tohost_addr = res.value().first;
+      auto status = memory_watcher->SetStoreWatchCallback(
+          AddressRange(tohost_addr),
+          [&riscv_top, tohost_addr, memory, &rv_state, &return_code, quiet](
+              uint64_t, int) -> void {
+            riscv_top.RequestHalt(RiscVTop::HaltReason::kProgramDone, nullptr);
+            auto *db = rv_state.db_factory()->Allocate<uint32_t>(1);
+            memory->Load(tohost_addr, db, nullptr, nullptr);
+            auto word = db->Get<uint32_t>(0);
+            db->DecRef();
+            return_code = word >> 1;
+            if (return_code == 0) {
+              if (!quiet) std::cerr << "PASS\n";
+            } else {
+            }
+          });
+      if (!status.ok()) {
+        std::cerr << "Error setting store watch callback for 'tohost': "
+                  << status.message();
+        return -1;
+      }
+    } else {
+      std::cerr << "Error: no symbol 'tohost' found";
+      return -1;
+    }
+  }
 
   // Initialize the PC to the entry point.
   uint32_t entry_point = load_result.value();
@@ -358,15 +415,16 @@
     }
   }
 
-  mpact::sim::util::MemoryWatcher *watcher = nullptr;
   RiscV32HtifSemiHost *htif_semihost = nullptr;
   if (absl::GetFlag(FLAGS_semihost_htif)) {
     // Add htif semihosting.
     RiscV32HtifSemiHost::SemiHostAddresses magic_addresses;
     if (GetMagicAddresses(&elf_loader, &magic_addresses)) {
-      watcher = new mpact::sim::util::MemoryWatcher(memory);
+      if (memory_watcher == nullptr) {
+        memory_watcher = new mpact::sim::util::MemoryWatcher(memory);
+      }
       htif_semihost = new RiscV32HtifSemiHost(
-          watcher, memory, magic_addresses,
+          memory_watcher, memory, magic_addresses,
           [&riscv_top]() {
             riscv_top.RequestHalt(RiscVTop::HaltReason::kSemihostHaltRequest,
                                   nullptr);
@@ -375,7 +433,7 @@
             riscv_top.RequestHalt(RiscVTop::HaltReason::kSemihostHaltRequest,
                                   nullptr);
           });
-      riscv_top.state()->set_memory(watcher);
+      riscv_top.state()->set_memory(memory_watcher);
     }
   }
 
@@ -422,7 +480,7 @@
         PrintRegisters);
     cmd_shell.Run(std::cin, std::cout);
   } else {
-    std::cerr << "Starting simulation\n";
+    if (!quiet) std::cerr << "Starting simulation\n";
 
     auto t0 = absl::Now();
 
@@ -441,8 +499,9 @@
     double sec = static_cast<double>(duration / absl::Milliseconds(100)) / 10;
     counter_sec.SetValue(sec);
 
-    std::cerr << absl::StrFormat("Simulation done: %0.1f sec\n", sec)
-              << std::endl;
+    if (!quiet)
+      std::cerr << absl::StrFormat("Simulation done: %0.1f sec\n", sec)
+                << std::endl;
   }
 
   // Export counters.
@@ -472,8 +531,9 @@
   }
   delete atomic_memory;
   delete memory;
-  delete watcher;
-  delete arm_semihost;
   delete htif_semihost;
+  delete memory_watcher;
+  delete arm_semihost;
   delete rv_decoder;
+  return return_code;
 }
diff --git a/riscv/rv32gv_sim.cc b/riscv/rv32gv_sim.cc
index 262a1b9..94d1e65 100644
--- a/riscv/rv32gv_sim.cc
+++ b/riscv/rv32gv_sim.cc
@@ -26,9 +26,11 @@
 #include <string>
 #include <vector>
 
+#include "absl/base/log_severity.h"
 #include "absl/flags/flag.h"
 #include "absl/flags/parse.h"
 #include "absl/log/check.h"
+#include "absl/log/globals.h"
 #include "absl/log/log.h"
 #include "absl/strings/str_cat.h"
 #include "absl/strings/str_format.h"
@@ -37,16 +39,19 @@
 #include "absl/time/time.h"
 #include "mpact/sim/generic/core_debug_interface.h"
 #include "mpact/sim/generic/counters.h"
+#include "mpact/sim/generic/decoder_interface.h"
 #include "mpact/sim/generic/instruction.h"
 #include "mpact/sim/proto/component_data.pb.h"
 #include "mpact/sim/util/memory/atomic_memory.h"
 #include "mpact/sim/util/memory/flat_demand_memory.h"
+#include "mpact/sim/util/memory/memory_interface.h"
 #include "mpact/sim/util/memory/memory_watcher.h"
 #include "mpact/sim/util/program_loader/elf_program_loader.h"
 #include "re2/re2.h"
 #include "riscv/debug_command_shell.h"
 #include "riscv/riscv32_htif_semihost.h"
 #include "riscv/riscv32g_vec_decoder.h"
+#include "riscv/riscv32gzb_vec_decoder.h"
 #include "riscv/riscv_arm_semihost.h"
 #include "riscv/riscv_fp_state.h"
 #include "riscv/riscv_register.h"
@@ -59,6 +64,7 @@
 using ::mpact::sim::generic::Instruction;
 using ::mpact::sim::proto::ComponentData;
 using ::mpact::sim::riscv::RiscV32GVecDecoder;
+using ::mpact::sim::riscv::RiscV32GZBVecDecoder;
 using ::mpact::sim::riscv::RiscV32HtifSemiHost;
 using ::mpact::sim::riscv::RiscVArmSemihost;
 using ::mpact::sim::riscv::RiscVFPState;
@@ -138,6 +144,19 @@
 // Exit on execution of ecall instruction, default false.
 ABSL_FLAG(bool, exit_on_ecall, false, "Exit on ecall - false by default");
 
+// Enable bit manipulation instructions.
+ABSL_FLAG(bool, bitmanip, false, "Enable bit manipulation instructions");
+
+// Exit on write to 'tohost'
+ABSL_FLAG(bool, exit_on_tohost, false, "Exit on write to 'tohost'");
+
+// Quiet mode. Suppress informational and warning messages.
+ABSL_FLAG(bool, quiet, false, "Suppress informational and warning messages");
+
+// Flag to enable and configure the instruction and data caches.
+ABSL_FLAG(std::string, icache, "", "Instruction cache configuration");
+ABSL_FLAG(std::string, dcache, "", "Data cache configuration");
+
 constexpr char kStackEndSymbolName[] = "__stack_end";
 constexpr char kStackSizeSymbolName[] = "__stack_size";
 
@@ -205,6 +224,7 @@
 }
 
 int main(int argc, char **argv) {
+  int return_code = 0;
   auto arg_vec = absl::ParseCommandLine(argc, argv);
 
   if (absl::GetFlag(FLAGS_semihost_htif) && absl::GetFlag(FLAGS_semihost_arm)) {
@@ -216,13 +236,26 @@
     std::cerr << "Only a single input file allowed" << std::endl;
     return -1;
   }
+
+  bool quiet = absl::GetFlag(FLAGS_quiet);
+  if (quiet) {
+    absl::SetMinLogLevel(absl::LogSeverityAtLeast::kError);
+  }
+
   std::string full_file_name = arg_vec[1];
   std::string file_name =
       full_file_name.substr(full_file_name.find_last_of('/') + 1);
   std::string file_basename = file_name.substr(0, file_name.find_first_of('.'));
 
   auto *memory = new mpact::sim::util::FlatDemandMemory();
-  auto *atomic_memory = new mpact::sim::util::AtomicMemory(memory);
+  mpact::sim::util::MemoryWatcher *memory_watcher = nullptr;
+  mpact::sim::util::AtomicMemory *atomic_memory = nullptr;
+  if (absl::GetFlag(FLAGS_exit_on_tohost)) {
+    memory_watcher = new mpact::sim::util::MemoryWatcher(memory);
+    atomic_memory = new mpact::sim::util::AtomicMemory(memory_watcher);
+  } else {
+    atomic_memory = new mpact::sim::util::AtomicMemory(memory);
+  }
   // Load the elf segments into memory.
   mpact::sim::util::ElfProgramLoader elf_loader(memory);
   auto load_result = elf_loader.LoadProgram(full_file_name);
@@ -232,6 +265,10 @@
     return -1;
   }
 
+  mpact::sim::util::MemoryInterface *memory_interface = memory;
+  if (memory_watcher != nullptr) {
+    memory_interface = memory_watcher;
+  }
   // Set up architectural state and decoder.
   RiscVState rv_state("RiscV32GV", RiscVXlen::RV32, memory, atomic_memory);
   // For floating point support add the fp state.
@@ -239,7 +276,12 @@
   RiscVVectorState rvv_state(&rv_state, 16 /*vector byte length*/);
   rv_state.set_rv_fp(&rv_fp_state);
   // Create the instruction decoder.
-  RiscV32GVecDecoder rv_decoder(&rv_state, memory);
+  mpact::sim::generic::DecoderInterface *rv_decoder = nullptr;
+  if (absl::GetFlag(FLAGS_bitmanip)) {
+    rv_decoder = new RiscV32GZBVecDecoder(&rv_state, memory);
+  } else {
+    rv_decoder = new RiscV32GVecDecoder(&rv_state, memory);
+  }
 
   // Make sure the architectural and abi register aliases are added.
   std::string reg_name;
@@ -256,7 +298,7 @@
         reg_name, ::mpact::sim::riscv::kFRegisterAliases[i]);
   }
 
-  RiscVTop riscv_top("RiscV32GVSim", &rv_state, &rv_decoder);
+  RiscVTop riscv_top("RiscV32GVSim", &rv_state, rv_decoder);
 
   if (absl::GetFlag(FLAGS_exit_on_ecall)) {
     rv_state.set_on_ecall([&riscv_top](const Instruction *inst) -> bool {
@@ -265,6 +307,37 @@
     });
   }
 
+  if (absl::GetFlag(FLAGS_exit_on_tohost)) {
+    auto res = elf_loader.GetSymbol("tohost");
+    if (res.ok()) {
+      auto tohost_addr = res.value().first;
+      auto status = memory_watcher->SetStoreWatchCallback(
+          AddressRange(tohost_addr),
+          [&riscv_top, tohost_addr, memory, &rv_state, &return_code, quiet](
+              uint64_t, int) -> void {
+            riscv_top.RequestHalt(RiscVTop::HaltReason::kProgramDone, nullptr);
+            auto *db = rv_state.db_factory()->Allocate<uint32_t>(1);
+            memory->Load(tohost_addr, db, nullptr, nullptr);
+            auto word = db->Get<uint32_t>(0);
+            db->DecRef();
+            return_code = word >> 1;
+            if (return_code == 0) {
+              if (!quiet) std::cerr << "PASS\n";
+            } else {
+              std::cerr << "** FAIL **\n";
+            }
+          });
+      if (!status.ok()) {
+        std::cerr << "Error setting store watch callback for 'tohost': "
+                  << status.message();
+        return -1;
+      }
+    } else {
+      std::cerr << "Error: no symbol 'tohost' found";
+      return -1;
+    }
+  }
+
   // Initialize the PC to the entry point.
   uint32_t entry_point = load_result.value();
   auto pc_write = riscv_top.WriteRegister("pc", entry_point);
@@ -323,15 +396,16 @@
     }
   }
 
-  mpact::sim::util::MemoryWatcher *watcher = nullptr;
   RiscV32HtifSemiHost *htif_semihost = nullptr;
   if (absl::GetFlag(FLAGS_semihost_htif)) {
     // Add htif semihosting.
     RiscV32HtifSemiHost::SemiHostAddresses magic_addresses;
     if (GetMagicAddresses(&elf_loader, &magic_addresses)) {
-      watcher = new mpact::sim::util::MemoryWatcher(memory);
+      if (memory_watcher == nullptr) {
+        memory_watcher = new mpact::sim::util::MemoryWatcher(memory);
+      }
       htif_semihost = new RiscV32HtifSemiHost(
-          watcher, memory, magic_addresses,
+          memory_watcher, memory, magic_addresses,
           [&riscv_top]() {
             riscv_top.RequestHalt(RiscVTop::HaltReason::kSemihostHaltRequest,
                                   nullptr);
@@ -340,7 +414,7 @@
             riscv_top.RequestHalt(RiscVTop::HaltReason::kSemihostHaltRequest,
                                   nullptr);
           });
-      riscv_top.state()->set_memory(watcher);
+      riscv_top.state()->set_memory(memory_watcher);
     }
   }
 
@@ -387,7 +461,7 @@
         PrintRegisters);
     cmd_shell.Run(std::cin, std::cout);
   } else {
-    std::cerr << "Starting simulation\n";
+    if (!quiet) std::cerr << "Starting simulation\n";
 
     auto t0 = absl::Now();
 
@@ -406,8 +480,9 @@
     double sec = static_cast<double>(duration / absl::Milliseconds(100)) / 10;
     counter_sec.SetValue(sec);
 
-    std::cerr << absl::StrFormat("Simulation done: %0.1f sec\n", sec)
-              << std::endl;
+    if (!quiet)
+      std::cerr << absl::StrFormat("Simulation done: %0.1f sec\n", sec)
+                << std::endl;
   }
 
   // Export counters.
@@ -437,7 +512,8 @@
   }
   delete atomic_memory;
   delete memory;
-  delete watcher;
-  delete arm_semihost;
   delete htif_semihost;
+  delete memory_watcher;
+  delete arm_semihost;
+  return return_code;
 }
diff --git a/riscv/rv64g_sim.cc b/riscv/rv64g_sim.cc
index e27ba69..71181b8 100644
--- a/riscv/rv64g_sim.cc
+++ b/riscv/rv64g_sim.cc
@@ -26,9 +26,11 @@
 #include <string>
 #include <vector>
 
+#include "absl/base/log_severity.h"
 #include "absl/flags/flag.h"
 #include "absl/flags/parse.h"
 #include "absl/log/check.h"
+#include "absl/log/globals.h"
 #include "absl/log/log.h"
 #include "absl/strings/str_cat.h"
 #include "absl/strings/str_format.h"
@@ -41,6 +43,7 @@
 #include "mpact/sim/proto/component_data.pb.h"
 #include "mpact/sim/util/memory/atomic_memory.h"
 #include "mpact/sim/util/memory/flat_demand_memory.h"
+#include "mpact/sim/util/memory/memory_interface.h"
 #include "mpact/sim/util/memory/memory_watcher.h"
 #include "mpact/sim/util/program_loader/elf_program_loader.h"
 #include "re2/re2.h"
@@ -134,6 +137,15 @@
 // Exit on execution of ecall instruction, default false.
 ABSL_FLAG(bool, exit_on_ecall, false, "Exit on ecall - false by default");
 
+// Enable bit manipulation instructions.
+ABSL_FLAG(bool, bitmanip, false, "Enable bit manipulation instructions");
+
+// Exit on write to 'tohost'
+ABSL_FLAG(bool, exit_on_tohost, false, "Exit on write to 'tohost'");
+
+// Quiet mode. Suppress informational and warning messages.
+ABSL_FLAG(bool, quiet, false, "Suppress informational and warning messages");
+
 constexpr char kStackEndSymbolName[] = "__stack_end";
 constexpr char kStackSizeSymbolName[] = "__stack_size";
 
@@ -179,6 +191,7 @@
 }
 
 int main(int argc, char **argv) {
+  int return_code = 0;
   auto arg_vec = absl::ParseCommandLine(argc, argv);
 
   if (absl::GetFlag(FLAGS_semihost_htif) && absl::GetFlag(FLAGS_semihost_arm)) {
@@ -190,13 +203,27 @@
     std::cerr << "Only a single input file allowed" << std::endl;
     return -1;
   }
+
+  bool quiet = absl::GetFlag(FLAGS_quiet);
+  if (quiet) {
+    absl::SetMinLogLevel(absl::LogSeverityAtLeast::kError);
+  }
+
   std::string full_file_name = arg_vec[1];
   std::string file_name =
       full_file_name.substr(full_file_name.find_last_of('/') + 1);
   std::string file_basename = file_name.substr(0, file_name.find_first_of('.'));
 
   auto *memory = new mpact::sim::util::FlatDemandMemory();
-  auto *atomic_memory = new mpact::sim::util::AtomicMemory(memory);
+  mpact::sim::util::MemoryWatcher *memory_watcher = nullptr;
+  mpact::sim::util::AtomicMemory *atomic_memory = nullptr;
+  if (absl::GetFlag(FLAGS_exit_on_tohost)) {
+    memory_watcher = new mpact::sim::util::MemoryWatcher(memory);
+    atomic_memory = new mpact::sim::util::AtomicMemory(memory_watcher);
+  } else {
+    atomic_memory = new mpact::sim::util::AtomicMemory(memory);
+  }
+
   // Load the elf segments into memory.
   mpact::sim::util::ElfProgramLoader elf_loader(memory);
   auto load_result = elf_loader.LoadProgram(full_file_name);
@@ -206,8 +233,14 @@
     return -1;
   }
 
+  mpact::sim::util::MemoryInterface *memory_interface = memory;
+  if (memory_watcher != nullptr) {
+    memory_interface = memory_watcher;
+  }
+
   // Set up architectural state and decoder.
-  RiscVState rv_state("RiscV64", RiscVXlen::RV64, memory, atomic_memory);
+  RiscVState rv_state("RiscV64", RiscVXlen::RV64, memory_interface,
+                      atomic_memory);
   // For floating point support add the fp state.
   RiscVFPState rv_fp_state(rv_state.csr_set(), &rv_state);
   rv_state.set_rv_fp(&rv_fp_state);
@@ -238,6 +271,37 @@
     });
   }
 
+  if (absl::GetFlag(FLAGS_exit_on_tohost)) {
+    auto res = elf_loader.GetSymbol("tohost");
+    if (res.ok()) {
+      auto tohost_addr = res.value().first;
+      auto status = memory_watcher->SetStoreWatchCallback(
+          AddressRange(tohost_addr),
+          [&riscv_top, tohost_addr, memory, &rv_state, &return_code, quiet](
+              uint64_t, int) -> void {
+            riscv_top.RequestHalt(RiscVTop::HaltReason::kProgramDone, nullptr);
+            auto *db = rv_state.db_factory()->Allocate<uint32_t>(1);
+            memory->Load(tohost_addr, db, nullptr, nullptr);
+            auto word = db->Get<uint32_t>(0);
+            db->DecRef();
+            return_code = word >> 1;
+            if (return_code == 0) {
+              if (!quiet) std::cerr << "PASS\n";
+            } else {
+              std::cerr << "** FAIL **\n";
+            }
+          });
+      if (!status.ok()) {
+        std::cerr << "Error setting store watch callback for 'tohost': "
+                  << status.message();
+        return -1;
+      }
+    } else {
+      std::cerr << "Error: no symbol 'tohost' found";
+      return -1;
+    }
+  }
+
   // Initialize the PC to the entry point.
   uint64_t entry_point = load_result.value();
   auto pc_write = riscv_top.WriteRegister("pc", entry_point);
@@ -340,7 +404,7 @@
         PrintRegisters);
     cmd_shell.Run(std::cin, std::cout);
   } else {
-    std::cerr << "Starting simulation\n";
+    if (!quiet) std::cerr << "Starting simulation\n";
 
     auto t0 = absl::Now();
 
@@ -359,8 +423,9 @@
     double sec = static_cast<double>(duration / absl::Milliseconds(100)) / 10;
     counter_sec.SetValue(sec);
 
-    std::cerr << absl::StrFormat("Simulation done: %0.1f sec\n", sec)
-              << std::endl;
+    if (!quiet)
+      std::cerr << absl::StrFormat("Simulation done: %0.1f sec\n", sec)
+                << std::endl;
   }
 
   // Export counters.
@@ -390,5 +455,7 @@
   }
   delete atomic_memory;
   delete memory;
+  delete memory_watcher;
   delete arm_semihost;
+  return return_code;
 }
diff --git a/riscv/rv64gv_sim.cc b/riscv/rv64gv_sim.cc
index 4ea7313..113fa6f 100644
--- a/riscv/rv64gv_sim.cc
+++ b/riscv/rv64gv_sim.cc
@@ -21,15 +21,16 @@
 #include <ios>
 #include <iostream>
 #include <memory>
-#include <new>
 #include <optional>
 #include <ostream>
 #include <string>
 #include <vector>
 
+#include "absl/base/log_severity.h"
 #include "absl/flags/flag.h"
 #include "absl/flags/parse.h"
 #include "absl/log/check.h"
+#include "absl/log/globals.h"
 #include "absl/log/log.h"
 #include "absl/strings/str_cat.h"
 #include "absl/strings/str_format.h"
@@ -43,6 +44,7 @@
 #include "mpact/sim/proto/component_data.pb.h"
 #include "mpact/sim/util/memory/atomic_memory.h"
 #include "mpact/sim/util/memory/flat_demand_memory.h"
+#include "mpact/sim/util/memory/memory_interface.h"
 #include "mpact/sim/util/memory/memory_watcher.h"
 #include "mpact/sim/util/program_loader/elf_program_loader.h"
 #include "re2/re2.h"
@@ -143,6 +145,12 @@
 // Enable bit manipulation instructions.
 ABSL_FLAG(bool, bitmanip, false, "Enable bit manipulation instructions");
 
+// Exit on write to 'tohost'
+ABSL_FLAG(bool, exit_on_tohost, false, "Exit on write to 'tohost'");
+
+// Quiet mode. Suppress informational and warning messages.
+ABSL_FLAG(bool, quiet, false, "Suppress informational and warning messages");
+
 constexpr char kStackEndSymbolName[] = "__stack_end";
 constexpr char kStackSizeSymbolName[] = "__stack_size";
 
@@ -188,6 +196,7 @@
 }
 
 int main(int argc, char **argv) {
+  int return_code = 0;
   auto arg_vec = absl::ParseCommandLine(argc, argv);
 
   if (absl::GetFlag(FLAGS_semihost_htif) && absl::GetFlag(FLAGS_semihost_arm)) {
@@ -199,13 +208,27 @@
     std::cerr << "Only a single input file allowed" << std::endl;
     return -1;
   }
+
+  bool quiet = absl::GetFlag(FLAGS_quiet);
+  if (quiet) {
+    absl::SetMinLogLevel(absl::LogSeverityAtLeast::kError);
+  }
+
   std::string full_file_name = arg_vec[1];
   std::string file_name =
       full_file_name.substr(full_file_name.find_last_of('/') + 1);
   std::string file_basename = file_name.substr(0, file_name.find_first_of('.'));
 
   auto *memory = new mpact::sim::util::FlatDemandMemory();
-  auto *atomic_memory = new mpact::sim::util::AtomicMemory(memory);
+  mpact::sim::util::MemoryWatcher *memory_watcher = nullptr;
+  mpact::sim::util::AtomicMemory *atomic_memory = nullptr;
+  if (absl::GetFlag(FLAGS_exit_on_tohost)) {
+    memory_watcher = new mpact::sim::util::MemoryWatcher(memory);
+    atomic_memory = new mpact::sim::util::AtomicMemory(memory_watcher);
+  } else {
+    atomic_memory = new mpact::sim::util::AtomicMemory(memory);
+  }
+
   // Load the elf segments into memory.
   mpact::sim::util::ElfProgramLoader elf_loader(memory);
   auto load_result = elf_loader.LoadProgram(full_file_name);
@@ -215,8 +238,13 @@
     return -1;
   }
 
+  mpact::sim::util::MemoryInterface *memory_interface = memory;
+  if (memory_watcher != nullptr) {
+    memory_interface = memory_watcher;
+  }
   // Set up architectural state and decoder.
-  RiscVState rv_state("RiscV64", RiscVXlen::RV64, memory, atomic_memory);
+  RiscVState rv_state("RiscV64", RiscVXlen::RV64, memory_interface,
+                      atomic_memory);
   // For floating point support add the fp state.
   RiscVFPState rv_fp_state(rv_state.csr_set(), &rv_state);
   rv_state.set_rv_fp(&rv_fp_state);
@@ -255,6 +283,37 @@
     });
   }
 
+  if (absl::GetFlag(FLAGS_exit_on_tohost)) {
+    auto res = elf_loader.GetSymbol("tohost");
+    if (res.ok()) {
+      auto tohost_addr = res.value().first;
+      auto status = memory_watcher->SetStoreWatchCallback(
+          AddressRange(tohost_addr),
+          [&riscv_top, tohost_addr, memory, &rv_state, &return_code, quiet](
+              uint64_t, int) -> void {
+            riscv_top.RequestHalt(RiscVTop::HaltReason::kProgramDone, nullptr);
+            auto *db = rv_state.db_factory()->Allocate<uint32_t>(1);
+            memory->Load(tohost_addr, db, nullptr, nullptr);
+            auto word = db->Get<uint32_t>(0);
+            db->DecRef();
+            return_code = word >> 1;
+            if (return_code == 0) {
+              if (!quiet) std::cerr << "PASS\n";
+            } else {
+              std::cerr << "** FAIL **\n";
+            }
+          });
+      if (!status.ok()) {
+        std::cerr << "Error setting store watch callback for 'tohost': "
+                  << status.message();
+        return -1;
+      }
+    } else {
+      std::cerr << "Error: no symbol 'tohost' found";
+      return -1;
+    }
+  }
+
   // Initialize the PC to the entry point.
   uint64_t entry_point = load_result.value();
   auto pc_write = riscv_top.WriteRegister("pc", entry_point);
@@ -357,7 +416,7 @@
         PrintRegisters);
     cmd_shell.Run(std::cin, std::cout);
   } else {
-    std::cerr << "Starting simulation\n";
+    if (!quiet) std::cerr << "Starting simulation\n";
 
     auto t0 = absl::Now();
 
@@ -376,8 +435,9 @@
     double sec = static_cast<double>(duration / absl::Milliseconds(100)) / 10;
     counter_sec.SetValue(sec);
 
-    std::cerr << absl::StrFormat("Simulation done: %0.1f sec\n", sec)
-              << std::endl;
+    if (!quiet)
+      std::cerr << absl::StrFormat("Simulation done: %0.1f sec\n", sec)
+                << std::endl;
   }
 
   // Export counters.
@@ -407,6 +467,8 @@
   }
   delete atomic_memory;
   delete memory;
+  delete memory_watcher;
   delete arm_semihost;
   delete rv_decoder;
+  return return_code;
 }
diff --git a/riscv/test/BUILD b/riscv/test/BUILD
index 2bc43a7..16e6c12 100644
--- a/riscv/test/BUILD
+++ b/riscv/test/BUILD
@@ -199,6 +199,7 @@
     tags = ["not_run:arm"],
     deps = [
         ":riscv_fp_test_base",
+        "//riscv:riscv_fp_state",
         "//riscv:riscv_g",
         "//riscv:riscv_state",
         "@com_google_googletest//:gtest_main",
diff --git a/riscv/test/riscv64_bitmanip_instructions_test.cc b/riscv/test/riscv64_bitmanip_instructions_test.cc
index e3ecefb..ecb82e9 100644
--- a/riscv/test/riscv64_bitmanip_instructions_test.cc
+++ b/riscv/test/riscv64_bitmanip_instructions_test.cc
@@ -606,7 +606,7 @@
     SetRegisterValues<uint64_t>({{kX1, val1}, {kX2, val2}});
     instruction_->Execute(nullptr);
     absl::uint128 result = 0;
-    for (int i = 0; i < sizeof(T) * 8; ++i) {
+    for (int i = 1; i < sizeof(T) * 8; ++i) {
       if (val2 & (1ULL << i)) result ^= (absl::MakeUint128(0, val1) << i);
     }
     EXPECT_EQ(GetRegisterValue<uint64_t>(kX3), absl::Uint128High64(result));
@@ -627,7 +627,7 @@
     SetRegisterValues<uint64_t>({{kX1, val1}, {kX2, val2}});
     instruction_->Execute(nullptr);
     uint64_t result = 0;
-    for (int i = 0; i < sizeof(T) * 8 - 1; ++i) {
+    for (int i = 0; i < sizeof(T) * 8; ++i) {
       if ((val2 >> i) & 1) result ^= (val1 >> (sizeof(T) * 8 - 1 - i));
     }
     EXPECT_EQ(GetRegisterValue<uint64_t>(kX3), result);
diff --git a/riscv/test/riscv_f_instructions_test.cc b/riscv/test/riscv_f_instructions_test.cc
index aa771ba..4beb278 100644
--- a/riscv/test/riscv_f_instructions_test.cc
+++ b/riscv/test/riscv_f_instructions_test.cc
@@ -21,6 +21,7 @@
 #include "googlemock/include/gmock/gmock.h"
 #include "mpact/sim/generic/instruction.h"
 #include "mpact/sim/generic/type_helpers.h"
+#include "riscv/riscv_fp_info.h"
 #include "riscv/test/riscv_fp_test_base.h"
 
 namespace {
diff --git a/riscv/test/riscv_fp_test_base.h b/riscv/test/riscv_fp_test_base.h
index cce8523..caec811 100644
--- a/riscv/test/riscv_fp_test_base.h
+++ b/riscv/test/riscv_fp_test_base.h
@@ -266,7 +266,7 @@
 inline typename std::enable_if<internal::LessSize<S, D>::value, D>::type NaNBox(
     S value) {
   using SInt = typename FPTypeInfo<S>::IntType;
-  SInt sval = *reinterpret_cast<SInt *>(&value);
+  SInt sval = absl::bit_cast<SInt>(value);
   D dval = (~static_cast<D>(0) << (sizeof(S) * 8)) | sval;
   return *reinterpret_cast<D *>(&dval);
 }
@@ -275,7 +275,7 @@
 template <typename S, typename D>
 inline typename std::enable_if<internal::EqualSize<S, D>::value, D>::type
 NaNBox(S value) {
-  return *reinterpret_cast<D *>(&value);
+  return absl::bit_cast<D>(value);
 }
 
 // Signal error if the register is smaller than the floating point value.
@@ -366,6 +366,23 @@
     }
   }
 
+  template <typename T, typename RegisterType = RV32Register>
+  void SetNaNBoxedRegisterValues(
+      const std::vector<std::tuple<std::string, const T>> &values) {
+    for (auto &[reg_name, value] : values) {
+      typename RegisterType::ValueType reg_value =
+          NaNBox<T, typename RegisterType::ValueType>(value);
+      LOG(INFO) << "Setting " << reg_name << " to " << std::hex << reg_value
+                << " from: " << value;
+      auto *reg = state_->GetRegister<RegisterType>(reg_name).first;
+      auto *db =
+          state_->db_factory()->Allocate<typename RegisterType::ValueType>(1);
+      db->template Set<typename RegisterType::ValueType>(0, reg_value);
+      reg->SetDataBuffer(db);
+      db->DecRef();
+    }
+  }
+
   // Initializes the semantic function of the instruction object.
   void SetSemanticFunction(Instruction *inst,
                            Instruction::SemanticFunction fcn) {
@@ -486,7 +503,7 @@
     *reinterpret_cast<LhsInt *>(&lhs_span[6]) = FPTypeInfo<LHS>::kPosDenorm;
     *reinterpret_cast<LhsInt *>(&lhs_span[7]) = FPTypeInfo<LHS>::kNegDenorm;
     for (int i = 0; i < kTestValueLength; i++) {
-      SetRegisterValues<LHS, LhsRegisterType>({{kR1Name, lhs_span[i]}});
+      SetNaNBoxedRegisterValues<LHS, LhsRegisterType>({{kR1Name, lhs_span[i]}});
 
       for (int rm : {0, 1, 2, 3, 4}) {
         rv_fp_->SetRoundingMode(static_cast<FPRoundingMode>(rm));
@@ -553,8 +570,8 @@
     *reinterpret_cast<LhsInt *>(&lhs_span[6]) = FPTypeInfo<LHS>::kPosDenorm;
     *reinterpret_cast<LhsInt *>(&lhs_span[7]) = FPTypeInfo<LHS>::kNegDenorm;
     for (int i = 0; i < kTestValueLength; i++) {
-      SetRegisterValues<LHS, LhsRegisterType>({{kR1Name, lhs_span[i]}});
-      SetRegisterValues<RHS, RhsRegisterType>({{kR2Name, rhs_span[i]}});
+      SetNaNBoxedRegisterValues<LHS, LhsRegisterType>({{kR1Name, lhs_span[i]}});
+      SetNaNBoxedRegisterValues<RHS, RhsRegisterType>({{kR2Name, rhs_span[i]}});
 
       for (int rm : {0, 1, 2, 3, 4}) {
         rv_fp_->SetRoundingMode(static_cast<FPRoundingMode>(rm));
@@ -615,8 +632,8 @@
     *reinterpret_cast<LhsInt *>(&lhs_span[6]) = FPTypeInfo<LHS>::kPosDenorm;
     *reinterpret_cast<LhsInt *>(&lhs_span[7]) = FPTypeInfo<LHS>::kNegDenorm;
     for (int i = 0; i < kTestValueLength; i++) {
-      SetRegisterValues<LHS, LhsRegisterType>({{kR1Name, lhs_span[i]}});
-      SetRegisterValues<RHS, RhsRegisterType>({{kR2Name, rhs_span[i]}});
+      SetNaNBoxedRegisterValues<LHS, LhsRegisterType>({{kR1Name, lhs_span[i]}});
+      SetNaNBoxedRegisterValues<RHS, RhsRegisterType>({{kR2Name, rhs_span[i]}});
 
       for (int rm : {0, 1, 2, 3, 4}) {
         rv_fp_->SetRoundingMode(static_cast<FPRoundingMode>(rm));
@@ -626,12 +643,24 @@
 
         inst->Execute(nullptr);
 
+        auto fflags = rv_fp_->fflags()->GetUint32();
+
         R op_val;
         uint32_t flag;
         {
           ScopedFPStatus set_fpstatus(rv_fp_->host_fp_interface());
           std::tie(op_val, flag) = operation(lhs_span[i], rhs_span[i]);
         }
+        if (name == "fmin") {
+          LOG(INFO) << "L: " << lhs_span[i] << " R: " << rhs_span[i];
+          LHS lhs_val = state_->GetRegister<LhsRegisterType>(kR1Name)
+                            .first->data_buffer()
+                            ->template Get<LHS>(0);
+          RHS rhs_val = state_->GetRegister<RhsRegisterType>(kR2Name)
+                            .first->data_buffer()
+                            ->template Get<RHS>(0);
+          LOG(INFO) << "Lreg: " << lhs_val << " Rreg: " << rhs_val;
+        }
         auto reg_val = state_->GetRegister<DestRegisterType>(kRdName)
                            .first->data_buffer()
                            ->template Get<R>(0);
@@ -640,7 +669,6 @@
             absl::StrCat(name, "  ", i, ": ", lhs_span[i], "  ", rhs_span[i]));
         auto lhs_uint = *reinterpret_cast<LhsUInt *>(&lhs_span[i]);
         auto rhs_uint = *reinterpret_cast<RhsUInt *>(&rhs_span[i]);
-        auto fflags = rv_fp_->fflags()->GetUint32();
         EXPECT_EQ(flag, fflags)
             << std::hex << name << "(" << lhs_uint << ", " << rhs_uint << ")";
       }
@@ -682,9 +710,9 @@
     *reinterpret_cast<LhsInt *>(&lhs_span[6]) = FPTypeInfo<LHS>::kPosDenorm;
     *reinterpret_cast<LhsInt *>(&lhs_span[7]) = FPTypeInfo<LHS>::kNegDenorm;
     for (int i = 0; i < kTestValueLength; i++) {
-      SetRegisterValues<LHS, LhsRegisterType>({{kR1Name, lhs_span[i]}});
-      SetRegisterValues<MHS, MhsRegisterType>({{kR2Name, mhs_span[i]}});
-      SetRegisterValues<RHS, RhsRegisterType>({{kR3Name, rhs_span[i]}});
+      SetNaNBoxedRegisterValues<LHS, LhsRegisterType>({{kR1Name, lhs_span[i]}});
+      SetNaNBoxedRegisterValues<MHS, MhsRegisterType>({{kR2Name, mhs_span[i]}});
+      SetNaNBoxedRegisterValues<RHS, RhsRegisterType>({{kR3Name, rhs_span[i]}});
 
       for (int rm : {0, 1, 2, 3, 4}) {
         rv_fp_->SetRoundingMode(static_cast<FPRoundingMode>(rm));
@@ -745,9 +773,9 @@
     *reinterpret_cast<LhsInt *>(&lhs_span[6]) = FPTypeInfo<LHS>::kPosDenorm;
     *reinterpret_cast<LhsInt *>(&lhs_span[7]) = FPTypeInfo<LHS>::kNegDenorm;
     for (int i = 0; i < kTestValueLength; i++) {
-      SetRegisterValues<LHS, LhsRegisterType>({{kR1Name, lhs_span[i]}});
-      SetRegisterValues<MHS, MhsRegisterType>({{kR2Name, mhs_span[i]}});
-      SetRegisterValues<RHS, RhsRegisterType>({{kR3Name, rhs_span[i]}});
+      SetNaNBoxedRegisterValues<LHS, LhsRegisterType>({{kR1Name, lhs_span[i]}});
+      SetNaNBoxedRegisterValues<MHS, MhsRegisterType>({{kR2Name, mhs_span[i]}});
+      SetNaNBoxedRegisterValues<RHS, RhsRegisterType>({{kR3Name, rhs_span[i]}});
 
       for (int rm : {0, 1, 2, 3, 4}) {
         rv_fp_->SetRoundingMode(static_cast<FPRoundingMode>(rm));
diff --git a/riscv/test/riscv_top_test.cc b/riscv/test/riscv_top_test.cc
index fc23d65..0176c02 100644
--- a/riscv/test/riscv_top_test.cc
+++ b/riscv/test/riscv_top_test.cc
@@ -114,9 +114,9 @@
   }
 
   ~HtifSemihostSetup() {
+    state_->set_memory(memory_);
     delete semihost_;
     delete watcher_;
-    state_->set_memory(memory_);
   }
 
  private: