Merge branch 'main' into mpact-deps-repo
diff --git a/external/BUILD.antlr4 b/external/BUILD.antlr4
index 994787d..920a68d 100644
--- a/external/BUILD.antlr4
+++ b/external/BUILD.antlr4
@@ -27,6 +27,9 @@
     cache_entries = {
         "CMAKE_CXX_FLAGS": "-std=c++17",
     },
+    generate_args = [
+        "-G Ninja",
+    ],
     lib_source = ":all_srcs",
     tags = ["requires-network"],
     out_static_libs = ["libantlr4-runtime.a"],
diff --git a/mpact/sim/decoder/bin_decoder.cc b/mpact/sim/decoder/bin_decoder.cc
index 04baa27..3eb1786 100644
--- a/mpact/sim/decoder/bin_decoder.cc
+++ b/mpact/sim/decoder/bin_decoder.cc
@@ -17,7 +17,6 @@
 #include <string>
 
 #include "mpact/sim/decoder/bin_encoding_info.h"
-#include "mpact/sim/decoder/instruction_encoding.h"
 #include "mpact/sim/decoder/instruction_group.h"
 
 namespace mpact {
@@ -38,7 +37,7 @@
   instruction_group_vec_.clear();
 }
 
-void BinDecoder::SelectInstructionGroupForDecoder(InstructionGroup *group) {
+void BinDecoder::AddInstructionGroup(InstructionGroup *group) {
   instruction_group_vec_.push_back(group);
 }
 
diff --git a/mpact/sim/decoder/bin_decoder.h b/mpact/sim/decoder/bin_decoder.h
index 285b8ff..e096ab8 100644
--- a/mpact/sim/decoder/bin_decoder.h
+++ b/mpact/sim/decoder/bin_decoder.h
@@ -46,7 +46,7 @@
   // Checks for invalid encodings, such as some duplicates.
   void CheckEncodings();
   // Select instruction group for decoder generation.
-  void SelectInstructionGroupForDecoder(InstructionGroup *group);
+  void AddInstructionGroup(InstructionGroup *group);
 
   // Accessors.
   const std::string &name() const { return name_; }
diff --git a/mpact/sim/decoder/bin_encoding_info.cc b/mpact/sim/decoder/bin_encoding_info.cc
index 435f447..ab700f1 100644
--- a/mpact/sim/decoder/bin_encoding_info.cc
+++ b/mpact/sim/decoder/bin_encoding_info.cc
@@ -33,7 +33,13 @@
                                  DecoderErrorListener *error_listener)
     : opcode_enum_(opcode_enum), error_listener_(error_listener) {}
 
-BinEncodingInfo::~BinEncodingInfo() { delete decoder_; }
+BinEncodingInfo::~BinEncodingInfo() {
+  delete decoder_;
+  for (auto &[unused, format_ptr] : format_map_) {
+    delete format_ptr;
+  }
+  format_map_.clear();
+}
 
 // Add name of an include file to be included in the generated code.
 void BinEncodingInfo::AddIncludeFile(std::string include_file) {
@@ -45,7 +51,7 @@
                                                     int width) {
   // Verify that the format name hasn't been used.
   if (format_map_.contains(name)) {
-    return absl::InternalError(
+    return absl::AlreadyExistsError(
         absl::StrCat("Error: format '", name, "' already defined"));
   }
   auto format = new Format(name, width, this);
@@ -58,7 +64,7 @@
                                                     std::string parent_name) {
   // Verify that the format name hasn't been used.
   if (format_map_.contains(name)) {
-    return absl::InternalError(
+    return absl::AlreadyExistsError(
         absl::StrCat("Error: format '", name, "' already defined"));
   }
   auto format = new Format(name, width, parent_name, this);
@@ -78,7 +84,7 @@
 absl::StatusOr<InstructionGroup *> BinEncodingInfo::AddInstructionGroup(
     std::string name, int width, std::string format_name) {
   if (instruction_group_map_.contains(name)) {
-    return absl::InternalError(
+    return absl::AlreadyExistsError(
         absl::StrCat("Error: instruction group '", name, "' already defined"));
   }
   auto group =
@@ -89,7 +95,7 @@
 
 // Top level method that calls the checking method of each format. This is
 // called after all the formats have been added.
-absl::Status BinEncodingInfo::CheckFormats() {
+void BinEncodingInfo::PropagateExtractors() {
   for (auto &[unused, format] : format_map_) {
     // For the base formats (those who do not inherit from another format).
     if (format->base_format() == nullptr) {
@@ -102,7 +108,6 @@
       format->PropagateExtractorsDown();
     }
   }
-  return absl::OkStatus();
 }
 
 BinDecoder *BinEncodingInfo::AddBinDecoder(std::string name) {
diff --git a/mpact/sim/decoder/bin_encoding_info.h b/mpact/sim/decoder/bin_encoding_info.h
index ee3cdff..6e3cee2 100644
--- a/mpact/sim/decoder/bin_encoding_info.h
+++ b/mpact/sim/decoder/bin_encoding_info.h
@@ -16,7 +16,6 @@
 #define MPACT_SIM_DECODER_BIN_ENCODING_INFO_H_
 
 #include <string>
-#include <vector>
 
 #include "absl/container/btree_map.h"
 #include "absl/container/btree_set.h"
@@ -59,10 +58,8 @@
   absl::StatusOr<InstructionGroup *> AddInstructionGroup(
       std::string name, int width, std::string format_name);
 
-  // Performs a consistency check on the formats and handles any out of order
-  // declarations - there is no rule that a format has to be declared before it
-  // is used.
-  absl::Status CheckFormats();
+  // Propagates bitfield extractors where possible.
+  void PropagateExtractors();
 
   // Create and add binary decoder descriptor.
   BinDecoder *AddBinDecoder(std::string name);
diff --git a/mpact/sim/decoder/bin_format_gen_main.cc b/mpact/sim/decoder/bin_format_gen_main.cc
index 3333d2e..30494fc 100644
--- a/mpact/sim/decoder/bin_format_gen_main.cc
+++ b/mpact/sim/decoder/bin_format_gen_main.cc
@@ -12,7 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include <fstream>
 #include <iostream>
 #include <ostream>
 #include <string>
@@ -39,11 +38,10 @@
 int main(int argc, char **argv) {
   auto arg_vec = absl::ParseCommandLine(argc, argv);
 
-  std::string file_name;
+  std::vector<std::string> file_names;
 
-  // Open input file as stream if specified.
-  if (arg_vec.size() > 1) {
-    file_name = arg_vec[1];
+  for (int i = 1; i < arg_vec.size(); ++i) {
+    file_names.push_back(arg_vec[i]);
   }
 
   ::mpact::sim::decoder::bin_format::BinFormatVisitor visitor;
@@ -61,7 +59,7 @@
     exit(-1);
   }
 
-  auto status = visitor.Process(file_name, decoder_name, prefix, include_roots,
+  auto status = visitor.Process(file_names, decoder_name, prefix, include_roots,
                                 output_dir);
   if (!status.ok()) {
     LOG(ERROR) << status.message();
diff --git a/mpact/sim/decoder/bin_format_visitor.cc b/mpact/sim/decoder/bin_format_visitor.cc
index c3fff36..468542c 100644
--- a/mpact/sim/decoder/bin_format_visitor.cc
+++ b/mpact/sim/decoder/bin_format_visitor.cc
@@ -27,7 +27,7 @@
 #include "absl/log/log.h"
 #include "absl/status/status.h"
 #include "absl/strings/str_cat.h"
-#include "absl/strings/str_split.h"
+#include "antlr4-runtime/ParserRuleContext.h"
 #include "mpact/sim/decoder/bin_decoder.h"
 #include "mpact/sim/decoder/bin_encoding_info.h"
 #include "mpact/sim/decoder/bin_format_contexts.h"
@@ -93,7 +93,7 @@
 using ::mpact::sim::machine_description::instruction_set::ToHeaderGuard;
 
 absl::Status BinFormatVisitor::Process(
-    const std::string &file_name, const std::string &decoder_name,
+    const std::vector<std::string> &file_names, const std::string &decoder_name,
     absl::string_view prefix, const std::vector<std::string> &include_roots,
     absl::string_view directory) {
   decoder_name_ = decoder_name;
@@ -105,22 +105,22 @@
 
   std::istream *source_stream = &std::cin;
 
-  if (!file_name.empty()) {
-    source_stream = new std::fstream(file_name, std::fstream::in);
+  if (!file_names.empty()) {
+    source_stream = new std::fstream(file_names[0], std::fstream::in);
   }
   // Create an antlr4 stream from the input stream.
   BinFmtAntlrParserWrapper parser_wrapper(source_stream);
 
   // Create and add the error listener.
   set_error_listener(std::make_unique<decoder::DecoderErrorListener>());
-  error_listener_->set_file_name(file_name);
+  error_listener_->set_file_name(file_names[0]);
   parser_wrapper.parser()->removeErrorListeners();
   parser_wrapper.parser()->addErrorListener(error_listener());
 
   // Parse the file and then create the data structures.
   TopLevelCtx *top_level = parser_wrapper.parser()->top_level();
   (void)top_level;
-  if (!file_name.empty()) {
+  if (!file_names.empty()) {
     delete source_stream;
     source_stream = nullptr;
   }
@@ -129,7 +129,13 @@
     return absl::InternalError("Errors encountered - terminating.");
   }
   // Visit the parse tree starting at the namespaces declaration.
-  auto encoding_info = VisitTopLevel(top_level, decoder_name);
+  VisitTopLevel(top_level);
+  // Process additional source files.
+  for (int i = 1; i < file_names.size(); ++i) {
+    ParseIncludeFile(top_level, file_names[i], {});
+  }
+  // Process the parse tree.
+  auto encoding_info = ProcessTopLevel(decoder_name);
   // Include files may generate additional syntax errors.
   if (encoding_info == nullptr) {
     LOG(ERROR) << "No encoding specified";
@@ -295,11 +301,14 @@
   return ret_val;
 }
 
-std::unique_ptr<BinEncodingInfo> BinFormatVisitor::VisitTopLevel(
-    TopLevelCtx *ctx, const std::string &decoder_name) {
+void BinFormatVisitor::VisitTopLevel(TopLevelCtx *ctx) {
   PreProcessDeclarations(ctx->declaration_list());
-  // At this point we have all the formats, instruction groups and decoders.
-  // First make sure that the named decoder exists.
+}
+
+std::unique_ptr<BinEncodingInfo> BinFormatVisitor::ProcessTopLevel(
+    const std::string &decoder_name) {
+  // At this point we have the contexts for all slots, bundles and isas.
+  // First make sure the named isa (decoder) has been defined.
   auto decoder_iter = decoder_decl_map_.find(decoder_name);
   if (decoder_iter == decoder_decl_map_.end()) {
     error_listener()->semanticError(
@@ -308,11 +317,7 @@
   }
   // Visit the decoder.
   auto bin_encoding_info = VisitDecoderDef(decoder_iter->second);
-  auto status = bin_encoding_info->CheckFormats();
-  if (!status.ok()) {
-    error_listener_->semanticError(nullptr, status.message());
-    return nullptr;
-  }
+  bin_encoding_info->PropagateExtractors();
   return bin_encoding_info;
 }
 
@@ -386,20 +391,30 @@
       return;
     }
   }
+  ParseIncludeFile(ctx, file_name, include_dir_vec_);
+}
+
+void BinFormatVisitor::ParseIncludeFile(antlr4::ParserRuleContext *ctx,
+                                        const std::string &file_name,
+                                        std::vector<std::string> const &dirs) {
   std::fstream include_file;
   // Open include file.
-  for (auto const &dir : include_dir_vec_) {
-    std::string include_name = absl::StrCat(dir, "/", file_name);
-    include_file.open(include_name, std::fstream::in);
-    if (include_file.is_open()) break;
-  }
+  include_file.open(file_name, std::fstream::in);
   if (!include_file.is_open()) {
-    // Try a local file.
-    include_file.open(file_name, std::fstream::in);
+    // Try each of the include file directories.
+    for (auto const &dir : dirs) {
+      std::string include_name = absl::StrCat(dir, "/", file_name);
+      include_file.open(include_name, std::fstream::in);
+      if (include_file.is_open()) break;
+    }
     if (!include_file.is_open()) {
-      error_listener()->semanticError(
-          ctx->start, absl::StrCat("Failed to open '", file_name, "'"));
-      return;
+      // Try a local file.
+      include_file.open(file_name, std::fstream::in);
+      if (!include_file.is_open()) {
+        error_listener()->semanticError(
+            ctx->start, absl::StrCat("Failed to open '", file_name, "'"));
+        return;
+      }
     }
   }
   std::string previous_file_name = error_listener()->file_name();
@@ -411,6 +426,7 @@
   // Add the error listener.
   include_parser->parser()->removeErrorListeners();
   include_parser->parser()->addErrorListener(error_listener());
+  // Start parsing at the declaratition_list_w_eof rule.
   DeclarationListCtx *declaration_list =
       include_parser->parser()->declaration_list_w_eof()->declaration_list();
   include_file.close();
@@ -788,7 +804,7 @@
         continue;
       }
       group_name_set.insert(group_name);
-      decoder->SelectInstructionGroupForDecoder(inst_group);
+      decoder->AddInstructionGroup(inst_group);
       continue;
     }
 
@@ -868,7 +884,7 @@
         }
       }
       group_name_set.insert(parent_group->name());
-      decoder->SelectInstructionGroupForDecoder(parent_group);
+      decoder->AddInstructionGroup(parent_group);
       continue;
     }
   }
diff --git a/mpact/sim/decoder/bin_format_visitor.h b/mpact/sim/decoder/bin_format_visitor.h
index 87d4bff..b2638bc 100644
--- a/mpact/sim/decoder/bin_format_visitor.h
+++ b/mpact/sim/decoder/bin_format_visitor.h
@@ -16,7 +16,6 @@
 #define MPACT_SIM_DECODER_BIN_FORMAT_VISITOR_H_
 
 #include <deque>
-#include <list>
 #include <memory>
 #include <string>
 #include <utility>
@@ -24,6 +23,7 @@
 
 #include "absl/container/flat_hash_map.h"
 #include "absl/status/status.h"
+#include "antlr4-runtime/ParserRuleContext.h"
 #include "mpact/sim/decoder/BinFormatLexer.h"
 #include "mpact/sim/decoder/antlr_parser_wrapper.h"
 #include "mpact/sim/decoder/bin_encoding_info.h"
@@ -67,7 +67,7 @@
 
   // Entry point for processing a source_stream input, generating any output
   // files in the given directory. Returns OK if no errors were encountered.
-  absl::Status Process(const std::string &file_name,
+  absl::Status Process(const std::vector<std::string> &file_names,
                        const std::string &decoder_name,
                        absl::string_view prefix,
                        const std::vector<std::string> &include_roots,
@@ -88,14 +88,18 @@
   BitRange GetBitIndexRange(BitIndexRangeCtx *ctx);
   int ConvertToInt(NumberCtx *ctx);
   // Methods that visit the nodes of the parse tree.
-  std::unique_ptr<BinEncodingInfo> VisitTopLevel(
-      TopLevelCtx *ctx, const std::string &decoder_name);
+  void VisitTopLevel(TopLevelCtx *ctx);
+  std::unique_ptr<BinEncodingInfo> ProcessTopLevel(
+      const std::string &decoder_name);
   void PreProcessDeclarations(DeclarationListCtx *ctx);
   void VisitDeclarations(DeclarationListCtx *ctx,
                          BinEncodingInfo *encoding_info);
   void VisitFormatDef(FormatDefCtx *ctx, BinEncodingInfo *encoding_info);
   void VisitFieldDef(FieldDefCtx *ctx, Format *format);
   void VisitIncludeFile(IncludeFileCtx *ctx);
+  void ParseIncludeFile(antlr4::ParserRuleContext *ctx,
+                        const std::string &file_name,
+                        const std::vector<std::string> &dirs);
   void VisitOverlayDef(OverlayDefCtx *ctx, Format *format);
   void VisitOverlayBitField(BitFieldCtx *ctx, Overlay *overlay);
   InstructionGroup *VisitInstructionGroupDef(InstructionGroupDefCtx *ctx,
diff --git a/mpact/sim/decoder/decoder_error_listener.cc b/mpact/sim/decoder/decoder_error_listener.cc
index 0bf5c2b..9a6e4c1 100644
--- a/mpact/sim/decoder/decoder_error_listener.cc
+++ b/mpact/sim/decoder/decoder_error_listener.cc
@@ -67,16 +67,21 @@
                                            bool exact,
                                            const antlrcpp::BitSet &ambigAlts,
                                            antlr4::atn::ATNConfigSet *configs) {
+  // Empty.
 }
 
 void DecoderErrorListener::reportAttemptingFullContext(
     antlr4::Parser *recognizer, const antlr4::dfa::DFA &dfa, size_t startIndex,
     size_t stopIndex, const antlrcpp::BitSet &conflictingAlts,
-    antlr4::atn::ATNConfigSet *configs) {}
+    antlr4::atn::ATNConfigSet *configs) {
+  // Empty.
+}
 
 void DecoderErrorListener::reportContextSensitivity(
     antlr4::Parser *recognizer, const antlr4::dfa::DFA &dfa, size_t startIndex,
-    size_t stopIndex, size_t prediction, antlr4::atn::ATNConfigSet *configs) {}
+    size_t stopIndex, size_t prediction, antlr4::atn::ATNConfigSet *configs) {
+  // Empty.
+}
 
 }  // namespace decoder
 }  // namespace sim
diff --git a/mpact/sim/decoder/decoder_gen_main.cc b/mpact/sim/decoder/decoder_gen_main.cc
index cdc7842..fe4588f 100644
--- a/mpact/sim/decoder/decoder_gen_main.cc
+++ b/mpact/sim/decoder/decoder_gen_main.cc
@@ -12,7 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include <fstream>
 #include <iostream>
 #include <ostream>
 #include <string>
@@ -57,11 +56,10 @@
 int main(int argc, char **argv) {
   auto arg_vec = absl::ParseCommandLine(argc, argv);
 
-  std::string file_name;
+  std::vector<std::string> file_names;
 
-  // Open input file as stream if specified.
-  if (arg_vec.size() > 1) {
-    file_name = arg_vec[1];
+  for (int i = 1; i < arg_vec.size(); ++i) {
+    file_names.push_back(arg_vec[i]);
   }
 
   mpact::sim::machine_description::instruction_set::InstructionSetVisitor
@@ -80,6 +78,11 @@
     exit(-1);
   }
   auto status =
-      visitor.Process(file_name, prefix, isa_name, include_roots, output_dir);
-  return status.ok() ? 0 : -1;
+      visitor.Process(file_names, prefix, isa_name, include_roots, output_dir);
+
+  if (!status.ok()) {
+    LOG(ERROR) << status.message();
+    return -1;
+  }
+  return 0;
 }
diff --git a/mpact/sim/decoder/encoding_group.cc b/mpact/sim/decoder/encoding_group.cc
index 9bdab50..1261de9 100644
--- a/mpact/sim/decoder/encoding_group.cc
+++ b/mpact/sim/decoder/encoding_group.cc
@@ -272,7 +272,7 @@
                       "None,");
     }
 
-    // Break the line every 4 iterms.
+    // Break the line every 4 items.
     if ((i % per_line) == per_line - 1) {
       absl::StrAppend(initializers_ptr, "\n");
     }
diff --git a/mpact/sim/decoder/format.cc b/mpact/sim/decoder/format.cc
index 3563c38..604a8f5 100644
--- a/mpact/sim/decoder/format.cc
+++ b/mpact/sim/decoder/format.cc
@@ -31,6 +31,16 @@
 
 using ::mpact::sim::machine_description::instruction_set::ToSnakeCase;
 
+FieldOrFormat::~FieldOrFormat() {
+  if (is_field_) {
+    delete field_;
+  } else {
+    delete format_;
+  }
+  field_ = nullptr;
+  format_ = nullptr;
+}
+
 // The equality operator compares to verify that the field/format definitions
 // are equivalent, i.e., refers to the same bits.
 bool FieldOrFormat::operator==(const FieldOrFormat &rhs) const {
@@ -61,9 +71,9 @@
       encoding_info_(encoding_info) {}
 
 Format::~Format() {
-  for (auto &[unused, field_ptr] : field_map_) {
-    delete field_ptr;
-  }
+  // for (auto &[unused, field_ptr] : field_map_) {
+  //   delete field_ptr;
+  // }
   field_map_.clear();
   for (auto &[unused, overlay_ptr] : overlay_map_) {
     delete overlay_ptr;
@@ -247,7 +257,7 @@
       // promoted.
       if (overlay_ptr == nullptr) continue;
       auto iter = base_format_->overlay_extractors_.find(name);
-      // If it isn't in the partent, add it.
+      // If it isn't in the parent, add it.
       if (iter == base_format_->overlay_extractors_.end()) {
         base_format_->overlay_extractors_.insert(
             std::make_pair(name, overlay_ptr));
diff --git a/mpact/sim/decoder/format.h b/mpact/sim/decoder/format.h
index 9d15d09..4cb1765 100644
--- a/mpact/sim/decoder/format.h
+++ b/mpact/sim/decoder/format.h
@@ -72,6 +72,7 @@
   explicit FieldOrFormat(Field *field) : is_field_(true), field_(field) {}
   FieldOrFormat(std::string fmt_name, int size, antlr4::Token *ctx)
       : is_field_(false), format_name_(fmt_name), size_(size), ctx_(ctx) {}
+  ~FieldOrFormat();
 
   bool is_field() const { return is_field_; }
   Field *field() const { return field_; }
diff --git a/mpact/sim/decoder/instruction_encoding.cc b/mpact/sim/decoder/instruction_encoding.cc
index 6db1035..f951c40 100644
--- a/mpact/sim/decoder/instruction_encoding.cc
+++ b/mpact/sim/decoder/instruction_encoding.cc
@@ -53,11 +53,37 @@
   // Check if the field name is indeed a field.
   auto *field = format_->GetField(field_name);
   if (field != nullptr) {
-    if (value >= (1 << field->width)) {
-      return absl::InternalError(
-          absl::StrCat("Constraint value (", value,
-                       ") exceeds field width for field '", field_name, "'"));
+    bool is_signed = field->is_signed;
+    if (!is_signed) {
+      if (value >= (1 << field->width) || (value < 0)) {
+        return absl::OutOfRangeError(absl::StrCat(
+            "Constraint value (", value, ") out of range for unsigned field '",
+            field_name, "'"));
+      }
+    } else {  // Field is signed.
+      // Only eq and ne constraints are allowed on signed fields.
+      if (type != ConstraintType::kEq && type != ConstraintType::kNe) {
+        return absl::InvalidArgumentError(
+            absl::StrCat("Only eq and ne constraints allowed on signed field: ",
+                         field_name));
+      }
+      // Check that the value is in range.
+      if (value < 0) {
+        int64_t min_value = -(1 << (field->width - 1));
+        if (value < min_value) {
+          return absl::OutOfRangeError(absl::StrCat(
+              "Constraint value (", value, ") out of range for signed field '",
+              field_name, "'"));
+        }
+      } else {
+        if (value >= (1 << (field->width - 1))) {
+          return absl::OutOfRangeError(absl::StrCat(
+              "Constraint value (", value, ") out of range for signed field '",
+              field_name, "'"));
+        }
+      }
     }
+    value &= (1 << field->width) - 1;
     auto *constraint = new Constraint();
     constraint->type = type;
     constraint->field = field;
@@ -68,14 +94,40 @@
   auto *overlay = format_->GetOverlay(field_name);
   if (overlay == nullptr) {
     // If neither, it's an error.
-    return absl::InternalError(absl::StrCat(
+    return absl::NotFoundError(absl::StrCat(
         "Format '", format_->name(),
         "' does not contain a field or overlay named ", field_name));
   }
-  if (value >= (1 << overlay->computed_width())) {
-    return absl::InternalError(absl::StrCat(
-        "Constraint value exceeds field width for field '", field_name, "'"));
+  int width = overlay->computed_width();
+  bool is_signed = overlay->is_signed();
+  if (!is_signed) {
+    if ((value >= (1 << width)) || (value < 0)) {
+      return absl::OutOfRangeError(absl::StrCat(
+          "Constraint value exceeds field width for field '", field_name, "'"));
+    }
+  } else {
+    if (type != ConstraintType::kEq && type != ConstraintType::kNe) {
+      return absl::InvalidArgumentError(
+          absl::StrCat("Only eq and ne constraints allowed on signed overlay: ",
+                       field_name));
+    }  // Check that the value is in range.
+    if (value < 0) {
+      int64_t min_value = -(1 << (width - 1));
+      if (value < min_value) {
+        return absl::OutOfRangeError(absl::StrCat(
+            "Constraint value (", value, ") out of range for signed overlay '",
+            field_name, "'"));
+      }
+      value = value & ((1 << width) - 1);
+    } else {
+      if (value >= (1 << (width - 1))) {
+        return absl::OutOfRangeError(absl::StrCat(
+            "Constraint value (", value, ") out of range for signed overlay '",
+            field_name, "'"));
+      }
+    }
   }
+  value &= (1 << width) - 1;
   auto *constraint = new Constraint();
   constraint->type = type;
   constraint->overlay = overlay;
diff --git a/mpact/sim/decoder/instruction_encoding.h b/mpact/sim/decoder/instruction_encoding.h
index 4c9d7b0..47fcc17 100644
--- a/mpact/sim/decoder/instruction_encoding.h
+++ b/mpact/sim/decoder/instruction_encoding.h
@@ -85,8 +85,8 @@
   const std::vector<Constraint *> &equal_extracted_constraints() const {
     return equal_extracted_constraints_;
   }
-  // The vector of not-equal constraints that have to be satisfied for an
-  // instruction to match this encoding.
+  // The vector of not-equal, greater, less, etc., constraints that have to be
+  // satisfied for an instruction to match this encoding.
   const std::vector<Constraint *> &other_constraints() const {
     return other_constraints_;
   }
diff --git a/mpact/sim/decoder/instruction_group.cc b/mpact/sim/decoder/instruction_group.cc
index 33b99a9..7971fc8 100644
--- a/mpact/sim/decoder/instruction_group.cc
+++ b/mpact/sim/decoder/instruction_group.cc
@@ -53,6 +53,10 @@
   }
   encoding_map_.clear();
   encoding_vec_.clear();
+  for (auto *group : encoding_group_vec_) {
+    delete group;
+  }
+  encoding_group_vec_.clear();
 }
 
 // Add an instruction encoding into the current group. Check that the format
diff --git a/mpact/sim/decoder/instruction_set_visitor.cc b/mpact/sim/decoder/instruction_set_visitor.cc
index eed28fc..a4f33dd 100644
--- a/mpact/sim/decoder/instruction_set_visitor.cc
+++ b/mpact/sim/decoder/instruction_set_visitor.cc
@@ -27,10 +27,8 @@
 #include <vector>
 
 #include "absl/container/flat_hash_set.h"
-#include "absl/log/log.h"
 #include "absl/status/status.h"
 #include "absl/strings/str_cat.h"
-#include "antlr4-runtime/antlr4-runtime.h"
 #include "mpact/sim/decoder/decoder_error_listener.h"
 #include "mpact/sim/decoder/format_name.h"
 #include "mpact/sim/decoder/template_expression.h"
@@ -75,7 +73,7 @@
 
 // Main entry point for processing the file.
 absl::Status InstructionSetVisitor::Process(
-    const std::string &file_name, const std::string &prefix,
+    const std::vector<std::string> &file_names, const std::string &prefix,
     const std::string &isa_name, const std::vector<std::string> &include_roots,
     absl::string_view directory) {
   if (isa_name.empty()) {
@@ -83,7 +81,6 @@
     return absl::InvalidArgumentError("Isa name cannot be empty");
   }
 
-  include_dir_vec_.push_back(".");
   for (auto &include_root : include_roots) {
     include_dir_vec_.push_back(include_root);
   }
@@ -91,21 +88,21 @@
   std::string isa_prefix = prefix;
   std::istream *source_stream = &std::cin;
 
-  if (!file_name.empty()) {
-    source_stream = new std::fstream(file_name, std::fstream::in);
+  if (!file_names.empty()) {
+    source_stream = new std::fstream(file_names[0], std::fstream::in);
   }
   // Create an antlr4 stream from the input stream.
   IsaAntlrParserWrapper parser_wrapper(source_stream);
   // Create and add the error listener.
   set_error_listener(std::make_unique<decoder::DecoderErrorListener>());
-  error_listener()->set_file_name(file_name);
+  error_listener()->set_file_name(file_names[0]);
   parser_wrapper.parser()->removeErrorListeners();
   parser_wrapper.parser()->addErrorListener(error_listener());
 
   // Parse the file and then create the data structures.
   TopLevelCtx *top_level = parser_wrapper.parser()->top_level();
 
-  if (!file_name.empty()) {
+  if (!file_names.empty()) {
     delete source_stream;
     source_stream = nullptr;
   }
@@ -115,7 +112,13 @@
   }
 
   // Visit the parse tree starting at the namespaces declaration.
-  auto instruction_set = VisitTopLevel(top_level, isa_name);
+  VisitTopLevel(top_level);
+  // Process additional source files.
+  for (int i = 1; i < file_names.size(); ++i) {
+    ParseIncludeFile(top_level, file_names[i], {});
+  }
+  // Now process the parse tree.
+  auto instruction_set = ProcessTopLevel(isa_name);
   // Include files may generate additional syntax errors.
   if (error_listener()->HasError() > 0) {
     return absl::InternalError("Errors encountered - terminating.");
@@ -135,8 +138,16 @@
   }
 
   // If the prefix is empty, use the source file name.
-  if (isa_prefix.empty()) {
-    isa_prefix = ToSnakeCase(std::filesystem::path(file_name).stem().string());
+  if (isa_prefix.empty() && file_names.empty()) {
+    error_listener()->semanticError(nullptr,
+                                    "No prefix or file name specified");
+  } else if (isa_prefix.empty()) {
+    isa_prefix =
+        ToSnakeCase(std::filesystem::path(file_names[0]).stem().string());
+  }
+  // Check for additional errors.
+  if (error_listener()->HasError() > 0) {
+    return absl::InternalError("Errors encountered - terminating.");
   }
   std::string encoding_type_name =
       absl::StrCat(ToPascalCase(isa_name), "EncodingBase");
@@ -220,8 +231,7 @@
   return absl::OkStatus();
 }
 
-std::unique_ptr<InstructionSet> InstructionSetVisitor::VisitTopLevel(
-    TopLevelCtx *ctx, const std::string &isa_name) {
+void InstructionSetVisitor::VisitTopLevel(TopLevelCtx *ctx) {
   auto declarations = ctx->declaration();
 
   // Process disasm widths. Only the one in the top level file is used if there
@@ -243,7 +253,10 @@
 
   // Parse, but don't process all the slots, bundles, isas and include files.
   PreProcessDeclarations(declarations);
+}
 
+std::unique_ptr<InstructionSet> InstructionSetVisitor::ProcessTopLevel(
+    absl::string_view isa_name) {
   // At this point we have the contexts for all isas, bundles, and slots.
   // First make sure that the named isa has been defined.
   auto isa_ptr = isa_decl_map_.find(isa_name);
@@ -455,17 +468,32 @@
       return;
     }
   }
+  ParseIncludeFile(ctx, file_name, include_dir_vec_);
+}
+
+void InstructionSetVisitor::ParseIncludeFile(
+    antlr4::ParserRuleContext *ctx, const std::string &file_name,
+    const std::vector<std::string> &dirs) {
   std::fstream include_file;
   // Open include file.
-  for (auto const &dir : include_dir_vec_) {
-    std::string include_name = absl::StrCat(dir, "/", file_name);
-    include_file.open(include_name, std::fstream::in);
-    if (include_file.is_open()) break;
-  }
+  include_file.open(file_name, std::fstream::in);
   if (!include_file.is_open()) {
-    error_listener()->semanticError(
-        ctx->start, absl::StrCat("Failed to open '", file_name, "'"));
-    return;
+    // Try each of the include file directories.
+    for (auto const &dir : dirs) {
+      std::string include_name = dir + "/" + file_name;
+      include_file.open(include_name, std::fstream::in);
+      if (include_file.is_open()) break;
+    }
+    if (!include_file.is_open()) {
+      if (ctx != nullptr) {
+        error_listener()->semanticError(
+            ctx->start, absl::StrCat("Failed to open '", file_name, "'"));
+      } else {
+        error_listener()->semanticError(
+            nullptr, absl::StrCat("Failed to open '", file_name, "'"));
+      }
+      return;
+    }
   }
   std::string previous_file_name = error_listener()->file_name();
   error_listener()->set_file_name(file_name);
@@ -477,9 +505,7 @@
   // Add the error listener.
   include_parser->parser()->removeErrorListeners();
   include_parser->parser()->addErrorListener(error_listener());
-  // Note, since include statements can only occur after the isa_def, don't
-  // parse starting at the top level rule, instead start at the
-  // declaration() rule.
+  // Start parsing at the include_top_level rule.
   auto declaration_vec =
       include_parser->parser()->include_top_level()->declaration();
   include_file.close();
diff --git a/mpact/sim/decoder/instruction_set_visitor.h b/mpact/sim/decoder/instruction_set_visitor.h
index a29d178..5c529f7 100644
--- a/mpact/sim/decoder/instruction_set_visitor.h
+++ b/mpact/sim/decoder/instruction_set_visitor.h
@@ -16,8 +16,6 @@
 #define MPACT_SIM_DECODER_INSTRUCTION_SET_VISITOR_H_
 
 #include <deque>
-#include <iostream>
-#include <istream>
 #include <memory>
 #include <optional>
 #include <string>
@@ -29,6 +27,7 @@
 #include "absl/container/flat_hash_set.h"
 #include "absl/status/status.h"
 #include "absl/strings/string_view.h"
+#include "antlr4-runtime/ParserRuleContext.h"
 #include "mpact/sim/decoder/InstructionSetLexer.h"
 #include "mpact/sim/decoder/InstructionSetParser.h"
 #include "mpact/sim/decoder/antlr_parser_wrapper.h"
@@ -67,8 +66,8 @@
 
   // Entry point for processing a source_stream input, generating any output
   // files in the given directory. Returns OK if no errors were encountered.
-  absl::Status Process(const std::string &file_name, const std::string &prefix,
-                       const std::string &isa_name,
+  absl::Status Process(const std::vector<std::string> &file_names,
+                       const std::string &prefix, const std::string &isa_name,
                        const std::vector<std::string> &include_roots,
                        absl::string_view directory);
 
@@ -123,8 +122,7 @@
   // The following methods visits the parts of the parse tree indicated by
   // the method name and builds up the internal representation used for
   // decoder generation.
-  std::unique_ptr<InstructionSet> VisitTopLevel(TopLevelCtx *ctx,
-                                                const std::string &isa_name);
+  void VisitTopLevel(TopLevelCtx *ctx);
 
   std::unique_ptr<InstructionSet> VisitIsaDeclaration(IsaDeclCtx *ctx);
   void VisitConstantDef(ConstantDefCtx *ctx);
@@ -154,6 +152,10 @@
       Slot *slot, Instruction *inst, ResourceItemCtx *resource_item);
   void VisitResourceDetailsLists(ResourceDetailsCtx *ctx, Slot *slot,
                                  Instruction *inst, ResourceSpec *spec);
+  std::unique_ptr<InstructionSet> ProcessTopLevel(absl::string_view isa_name);
+  void ParseIncludeFile(antlr4::ParserRuleContext *ctx,
+                        const std::string &file_name,
+                        const std::vector<std::string> &dirs);
   DestinationOperand *FindDestinationOpInExpression(
       ExpressionCtx *ctx, const Slot *slot, const Instruction *inst) const;
   void PerformOpcodeOverrides(
diff --git a/mpact/sim/decoder/mpact_sim_isa.bzl b/mpact/sim/decoder/mpact_sim_isa.bzl
index c0f0f03..2639933 100644
--- a/mpact/sim/decoder/mpact_sim_isa.bzl
+++ b/mpact/sim/decoder/mpact_sim_isa.bzl
@@ -51,21 +51,31 @@
         data = data,
     )
 
-def mpact_isa_decoder(name, src, includes, deps = [], isa_name = "", prefix = ""):
+def mpact_isa_decoder(name, includes, src = "", srcs = [], deps = [], isa_name = "", prefix = ""):
     """Generates the C++ source corresponding to an MPACT Isa decoder definition.
 
     Args:
       name: The name of the package to use for the cc_library.
-      src:  The .isa file containing the Isa rules.
+      src:  The .isa file containing the Isa rules (or use srcs).
+      srcs: The .isa files containing the Isa rules (if src is not specified).
       deps: Dependencies for the cc_library.
       includes: Include .isa files.
       isa_name: Name of isa to generate code for.
       prefix: File prefix for the generated files (otherwise uses base name of src file
     """
-    if not src.endswith(".isa"):
-        fail("Grammar must end with .isa", "src")
+
+    # if src is not empty, prepend it to the srcs list.
+    if src != "":
+        isa_srcs = [src] + srcs
+    else:
+        isa_srcs = srcs
+
+    for f in isa_srcs:
+        if not f.endswith(".isa"):
+            fail("Grammar file '" + f + "' must end with .isa", "src")
+
     if prefix == "":
-        file_prefix = src[:-4]
+        file_prefix = isa_srcs[0][:-4]
         base_file_prefix = _strip_path(file_prefix)
     else:
         base_file_prefix = prefix
@@ -80,15 +90,15 @@
 
     # The command to generate the files.
     command = ";\n".join([
-        _make_isa_tool_invocation_command(base_file_prefix, isa_name),
+        _make_isa_tool_invocation_command(len(isa_srcs), base_file_prefix, isa_name),
     ])
 
     # The rule for the generated sources.
     native.genrule(
         name = name + "_source",
-        # Add includes to srcs to ensure they are added to the blaze build sandbox where
+        # Add includes to isa_srcs to ensure they are added to the blaze build sandbox where
         # they can be found.
-        srcs = [src] + includes,
+        srcs = isa_srcs + includes,
         outs = out_files,
         cmd = command,
         heuristic_label_expansion = 0,
@@ -108,19 +118,28 @@
         ] + deps,
     )
 
-def mpact_bin_fmt_decoder(name, src, includes, deps = [], decoder_name = "", prefix = ""):
+def mpact_bin_fmt_decoder(name, includes, src = "", srcs = [], deps = [], decoder_name = "", prefix = ""):
     """Generates the C++ source corresponding to an MPACT Bin Format decoder definition.
 
     Args:
       name: The name of the package to use for the cc_library.
-      src:  The .bin_fmt file containing the Bin Format rules.
       includes: Include .bin_fmt files.
+      src:  The .bin_fmt file containing the Bin Format rules.
+      srcs: List of .bin_fmt files containing the Bin Format rules.
       deps: Dependencies for the cc_library
       decoder_name: Name of decoder from .bin_fmt file to generate
       prefix: File prefix for the generated files (otherwise uses base name of src file
     """
-    if not src.endswith(".bin_fmt"):
-        fail("Grammar must end with .bin_fmt", "src")
+
+    if src != "":
+        bin_srcs = [src] + srcs
+    else:
+        bin_srcs = srcs
+
+    for f in bin_srcs:
+        if not f.endswith(".bin_fmt"):
+            fail("Grammar file '" + f + "' must end with .bin_fmt", "srcs")
+
     if prefix == "":
         file_prefix = src[:-8]
         base_file_prefix = _strip_path(file_prefix)
@@ -135,7 +154,7 @@
 
     # The command to generate the files.
     command = ";\n".join([
-        _make_bin_tool_invocation_command(base_file_prefix, decoder_name),
+        _make_bin_tool_invocation_command(len(bin_srcs), base_file_prefix, decoder_name),
     ])
 
     # The rule for the generated sources.
@@ -171,26 +190,32 @@
         return text
     return text[pos + 1:]
 
-# Create the decoder_gen command with arguments, Since the srcs had all the files, including
-# those that will be included, the command includes creating a bash array from $(SRCS), then
-# instead of using $(SRCS) in the command, it uses only the first element of that array.
-def _make_isa_tool_invocation_command(prefix, isa_name):
-    cmd = "ARR=($(SRCS)); $(location @@com_google_mpact-sim//mpact/sim/decoder:decoder_gen) " + \
-          "$${ARR[0]}" + \
-          " --prefix " + prefix + \
-          " --output_dir $(@D)"
+# Create the decoder_gen command with arguments, Since the srcs had all the
+# files, including those that will be included, the command includes creating
+# a bash array from $(SRCS), then instead of using $(SRCS) in the command, it
+# uses only the first element of that array.
+def _make_isa_tool_invocation_command(num_srcs, prefix, isa_name):
+    cmd = "ARR=($(SRCS)); $(location //external:decoder_gen) "
+
+    # Add the sources that are not in includes.
+    for i in range(0, num_srcs):
+        cmd += "$${ARR[" + str(i) + "]} "
+    cmd += "--prefix " + prefix + " --output_dir $(@D)"
     if isa_name != "":
         cmd += " --isa_name " + isa_name
+
     return cmd
 
 # Create the bin_format_gen command with arguments, Since the srcs had all the files, including
 # those that will be included, the command includes creating a bash array from $(SRCS), then
 # instead of using $(SRCS) in the command, it uses only the first element of that array.
-def _make_bin_tool_invocation_command(prefix, decoder_name):
-    cmd = "ARR=($(SRCS)); $(location @@com_google_mpact-sim//mpact/sim/decoder:bin_format_gen) " + \
-          "$${ARR[0]}" + \
-          " --prefix " + prefix + \
-          " --output_dir $(@D)"
+def _make_bin_tool_invocation_command(num_srcs, prefix, decoder_name):
+    cmd = "ARR=($(SRCS)); $(location //external:bin_format_gen) "
+
+    # Add the sources that are not in includes.
+    for i in range(0, num_srcs):
+        cmd += "$${ARR[" + str(i) + "]} "
+    cmd += " --prefix " + prefix + " --output_dir $(@D)"
     if decoder_name != "":
         cmd += " --decoder_name " + decoder_name
     return cmd
diff --git a/mpact/sim/decoder/test/BUILD b/mpact/sim/decoder/test/BUILD
index ba3adad..91222dd 100644
--- a/mpact/sim/decoder/test/BUILD
+++ b/mpact/sim/decoder/test/BUILD
@@ -82,6 +82,21 @@
 )
 
 mpact_cc_test(
+    name = "resource_test",
+    size = "small",
+    srcs = [
+        "resource_test.cc",
+    ],
+    deps = [
+        "//mpact/sim/decoder:isa_parser",
+        "@com_google_absl//absl/container:btree",
+        "@com_google_absl//absl/status:statusor",
+        "@com_google_googletest//:gtest",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+mpact_cc_test(
     name = "template_expression_test",
     size = "small",
     srcs = [
@@ -137,6 +152,19 @@
     isa_name = "Example",
 )
 
+mpact_isa_decoder(
+    name = "combined_isa",
+    srcs = [
+        "testfiles/bundle_b.isa",
+        "testfiles/part1.isa",
+    ],
+    includes = [
+        "//mpact/sim/decoder/test/testfiles/include:bundle_a.isa",
+        "//mpact/sim/decoder/test/testfiles/include:empty_include.isa",
+    ],
+    isa_name = "Example",
+)
+
 mpact_cc_library(
     name = "riscv32i",
     hdrs = ["testfiles/riscv32i.h"],
@@ -155,10 +183,10 @@
     deps = ["riscv32i"],
 )
 
-# The point of this test is to actually run the generator on the testfiles/example.isa
-# grammar file, generate source files, and then compile the generated files and
-# link into this test case. Provided that succeeds, this test case succeeds. See the
-# :example_isa build rule for build options.
+# The point of this test is to actually run the generator on the
+# testfiles/example.isa grammar file, generate source files, and then compile
+# the generated files and link into this test case. Provided that succeeds,
+# this test case succeeds. See the :example_isa build rule for build options.
 mpact_cc_test(
     name = "example_decoder_test",
     size = "small",
@@ -175,6 +203,26 @@
     ],
 )
 
+# The point of this test is to actually run the generator on the
+# testfiles/part1.isa grammar file, generate source files, and then compile
+# the generated files and link into this test case. Provided that succeeds,
+# this test case succeeds. See the :example_isa build rule for build options.
+mpact_cc_test(
+    name = "combined_decoder_test",
+    size = "small",
+    srcs = [
+        "combined_decoder_test.cc",
+    ],
+    deps = [
+        ":combined_isa",
+        "//mpact/sim/generic:arch_state",
+        "//mpact/sim/generic:instruction",
+        "@com_google_absl//absl/container:flat_hash_map",
+        "@com_google_googletest//:gtest",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
 mpact_cc_test(
     name = "extract_bits_test",
     size = "small",
@@ -233,3 +281,61 @@
         "@com_google_googletest//:gtest_main",
     ],
 )
+
+mpact_cc_test(
+    name = "bin_encoding_info_test",
+    size = "small",
+    srcs = [
+        "bin_encoding_info_test.cc",
+    ],
+    deps = [
+        "//mpact/sim/decoder:bin_format_visitor",
+        "//mpact/sim/decoder:decoder_error_listener",
+        "@com_google_absl//absl/container:btree",
+        "@com_google_absl//absl/log:check",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/status:statusor",
+        "@com_google_absl//absl/strings",
+        "@com_google_googletest//:gtest",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+mpact_cc_test(
+    name = "instruction_encoding_test",
+    size = "small",
+    srcs = [
+        "instruction_encoding_test.cc",
+    ],
+    deps = [
+        "//mpact/sim/decoder:bin_format_visitor",
+        "//mpact/sim/decoder:decoder_error_listener",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/status:statusor",
+        "@com_google_googletest//:gtest",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+mpact_cc_test(
+    name = "bin_format_visitor_test",
+    size = "small",
+    srcs = [
+        "bin_format_visitor_test.cc",
+    ],
+    data = [
+        # Local include files.
+        "testfiles/empty_file.bin_fmt",
+        "testfiles/riscv32_top.bin_fmt",
+        "testfiles/riscv32g.bin_fmt",
+        "testfiles/riscv32c.bin_fmt",
+    ],
+    deps = [
+        "//mpact/sim/decoder:bin_format_visitor",
+        "@com_google_absl//absl/flags:flag",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/strings",
+        "@com_google_googletest//:gtest",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
diff --git a/mpact/sim/decoder/test/bin_encoding_info_test.cc b/mpact/sim/decoder/test/bin_encoding_info_test.cc
new file mode 100644
index 0000000..b555daa
--- /dev/null
+++ b/mpact/sim/decoder/test/bin_encoding_info_test.cc
@@ -0,0 +1,148 @@
+#include "mpact/sim/decoder/bin_encoding_info.h"
+
+#include "absl/log/check.h"
+#include "absl/status/status.h"
+#include "googletest/include/gtest/gtest.h"
+#include "mpact/sim/decoder/decoder_error_listener.h"
+#include "mpact/sim/decoder/format.h"
+#include "mpact/sim/decoder/instruction_group.h"
+
+// This file provides for testing of the BinEncodingInfo class interfaces, with
+// the exception of PropagateExtractors, as that cannot be tested in isolation.
+
+namespace {
+
+using ::mpact::sim::decoder::DecoderErrorListener;
+using ::mpact::sim::decoder::bin_format::BinEncodingInfo;
+
+constexpr char kOpcodeEnumName[] = "OpcodeEnumName";
+constexpr char kIncludeFile0[] = "IncludeFile0";
+constexpr char kIncludeFile1[] = "IncludeFile1";
+constexpr char kIncludeFile2[] = "IncludeFile2";
+constexpr const char *kIncludeFiles[] = {kIncludeFile0, kIncludeFile1,
+                                         kIncludeFile2};
+constexpr char kFormat0[] = "Format0";
+constexpr char kFormat1[] = "Format1";
+constexpr char kFormat2[] = "Format2";
+constexpr char kGroup0[] = "Group0";
+constexpr char kBinDecoder[] = "BinDecoder";
+constexpr int kFormatWidth32 = 32;
+constexpr int kFormatWidth16 = 16;
+
+class BinEncodingInfoTest : public ::testing::Test {
+ protected:
+  BinEncodingInfoTest() {
+    error_listener_ = new DecoderErrorListener();
+    bin_encoding_info_ = new BinEncodingInfo(kOpcodeEnumName, error_listener_);
+  }
+  ~BinEncodingInfoTest() override {
+    for (auto &[name, instruction_group_ptr] :
+         bin_encoding_info_->instruction_group_map()) {
+      delete instruction_group_ptr;
+    }
+    delete bin_encoding_info_;
+    delete error_listener_;
+  }
+
+  DecoderErrorListener *error_listener_ = nullptr;
+  BinEncodingInfo *bin_encoding_info_ = nullptr;
+};
+
+// Test for proper initialization of object.
+TEST_F(BinEncodingInfoTest, Constructed) {
+  EXPECT_FALSE(error_listener_->HasError());
+  EXPECT_TRUE(bin_encoding_info_->format_map().empty());
+  EXPECT_TRUE(bin_encoding_info_->include_files().empty());
+  EXPECT_TRUE(bin_encoding_info_->instruction_group_map().empty());
+  EXPECT_EQ(bin_encoding_info_->decoder(), nullptr);
+  EXPECT_EQ(bin_encoding_info_->error_listener(), error_listener_);
+}
+
+// Test that include files are properly added/kept.
+TEST_F(BinEncodingInfoTest, AddIncludeFile) {
+  EXPECT_TRUE(bin_encoding_info_->include_files().empty());
+  for (auto const &include_file : kIncludeFiles) {
+    bin_encoding_info_->AddIncludeFile(include_file);
+  }
+  EXPECT_FALSE(bin_encoding_info_->include_files().empty());
+  for (auto const &include_file : kIncludeFiles) {
+    EXPECT_TRUE(bin_encoding_info_->include_files().contains(include_file));
+  }
+  EXPECT_FALSE(bin_encoding_info_->include_files().contains("NoIncludeFile"));
+}
+
+// Test that formats are properly added/kept.
+TEST_F(BinEncodingInfoTest, AddFormat) {
+  // Adding a new format should work.
+  auto res0 = bin_encoding_info_->AddFormat(kFormat0, kFormatWidth32);
+  EXPECT_TRUE(res0.status().ok());
+  auto *format = res0.value();
+  EXPECT_EQ(format->name(), kFormat0);
+  EXPECT_EQ(format->declared_width(), kFormatWidth32);
+
+  // Make sure we get the format back when calling GetFormat.
+  auto *get_format = bin_encoding_info_->GetFormat(kFormat0);
+  EXPECT_EQ(get_format, format);
+  // Adding the same format again should fail.
+  res0 = bin_encoding_info_->AddFormat(kFormat0, kFormatWidth32);
+  EXPECT_EQ(absl::StatusCode::kAlreadyExists, res0.status().code());
+
+  // Add a different format should work.
+  auto res1 = bin_encoding_info_->AddFormat(kFormat1, kFormatWidth16);
+  EXPECT_TRUE(res1.status().ok());
+  format = res1.value();
+  EXPECT_EQ(format->name(), kFormat1);
+  EXPECT_EQ(format->declared_width(), kFormatWidth16);
+
+  // Add format with parent.
+  auto res2 = bin_encoding_info_->AddFormat(kFormat2, kFormatWidth32, kFormat0);
+  EXPECT_TRUE(res2.status().ok());
+  format = res2.value();
+  EXPECT_EQ(format->name(), kFormat2);
+  EXPECT_EQ(format->declared_width(), kFormatWidth32);
+
+  // Can't add the same format twice.
+  res2 = bin_encoding_info_->AddFormat(kFormat2, kFormatWidth32, kFormat0);
+  EXPECT_EQ(absl::StatusCode::kAlreadyExists, res2.status().code());
+
+  // Format map. Verify that the formats are in the map.
+  auto &format_map = bin_encoding_info_->format_map();
+  EXPECT_EQ(format_map.size(), 3);
+  EXPECT_NE(format_map.find(kFormat0), format_map.end());
+  EXPECT_NE(format_map.find(kFormat1), format_map.end());
+  EXPECT_NE(format_map.find(kFormat2), format_map.end());
+}
+
+// Instruction groups.
+TEST_F(BinEncodingInfoTest, AddInstructionGroup) {
+  EXPECT_TRUE(bin_encoding_info_->instruction_group_map().empty());
+
+  // Add an instruction group.
+  auto res = bin_encoding_info_->AddInstructionGroup(kGroup0, kFormatWidth32,
+                                                     kFormat0);
+  EXPECT_TRUE(res.status().ok());
+  auto *instruction_group = res.value();
+  EXPECT_EQ(instruction_group->name(), kGroup0);
+  EXPECT_EQ(instruction_group->width(), kFormatWidth32);
+  EXPECT_EQ(instruction_group->format_name(), kFormat0);
+  EXPECT_EQ(instruction_group->opcode_enum(), kOpcodeEnumName);
+
+  // Adding it a second time doesn't work.
+  res = bin_encoding_info_->AddInstructionGroup(kGroup0, kFormatWidth32,
+                                                kFormat0);
+  EXPECT_EQ(absl::StatusCode::kAlreadyExists, res.status().code());
+}
+
+// Bin decoder.
+TEST_F(BinEncodingInfoTest, AddDecoder) {
+  // Add BinDecoder.
+  auto *bin_dec = bin_encoding_info_->AddBinDecoder(kBinDecoder);
+  EXPECT_NE(bin_dec, nullptr);
+  EXPECT_FALSE(error_listener_->HasError());
+  // Try adding it again.
+  bin_dec = bin_encoding_info_->AddBinDecoder(kBinDecoder);
+  EXPECT_EQ(bin_dec, nullptr);
+  EXPECT_TRUE(error_listener_->HasError());
+}
+
+}  // namespace
diff --git a/mpact/sim/decoder/test/bin_format_visitor_test.cc b/mpact/sim/decoder/test/bin_format_visitor_test.cc
new file mode 100644
index 0000000..7cee644
--- /dev/null
+++ b/mpact/sim/decoder/test/bin_format_visitor_test.cc
@@ -0,0 +1,95 @@
+// 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 "mpact/sim/decoder/bin_format_visitor.h"
+
+#include <string>
+
+#include "absl/strings/str_cat.h"
+#include "googletest/include/gtest/gtest.h"
+
+namespace {
+
+constexpr char kTestUndeclaredOutputsDir[] = "TEST_UNDECLARED_OUTPUTS_DIR";
+
+constexpr char kEmptyDecoderName[] = "Empty";
+constexpr char kEmptyBaseName[] = "empty_file";
+constexpr char kRiscVDecoderName[] = "RiscV32G";
+constexpr char kRiscVBaseName[] = "riscv32";
+constexpr char kRiscVTopName[] = "riscv32_top.bin_fmt";
+constexpr char kRiscV32GName[] = "riscv32g.bin_fmt";
+constexpr char kRiscV32CName[] = "riscv32c.bin_fmt";
+
+using ::mpact::sim::decoder::bin_format::BinFormatVisitor;
+
+// The depot path to the test directory.
+constexpr char kDepotPath[] = "mpact/sim/decoder/test/";
+
+std::string OutputDir() { return "./"; }
+
+static bool FileExists(const std::string &name) {
+  std::ifstream file(name);
+  return file.good();
+}
+
+class BinFormatParserTest : public testing::Test {
+ protected:
+  BinFormatParserTest() {}
+
+  std::vector<std::string> paths_;
+};
+
+TEST_F(BinFormatParserTest, NullFileParsing) {
+  // Set up input and output file paths.
+  std::vector<std::string> input_files = {
+      absl::StrCat(kDepotPath, "testfiles/", kEmptyBaseName, ".bin_fmt")};
+  ASSERT_TRUE(FileExists(input_files[0]));
+  std::string output_dir = OutputDir();
+
+  BinFormatVisitor visitor;
+  // Parse and process the input file.
+  EXPECT_FALSE(visitor
+                   .Process(input_files, kEmptyBaseName, kEmptyDecoderName,
+                            paths_, output_dir)
+                   .ok());
+}
+
+TEST_F(BinFormatParserTest, BasicParsing) {
+  // Make sure the visitor can read and parse the input file.
+  // Set up input and output file paths.
+  std::vector<std::string> input_files = {
+      absl::StrCat(kDepotPath, "testfiles/", kRiscVTopName),
+      absl::StrCat(kDepotPath, "testfiles/", kRiscV32GName),
+      absl::StrCat(kDepotPath, "testfiles/", kRiscV32CName),
+  };
+  ASSERT_TRUE(FileExists(input_files[0]));
+  ASSERT_TRUE(FileExists(input_files[1]));
+  ASSERT_TRUE(FileExists(input_files[2]));
+  std::string output_dir = getenv(kTestUndeclaredOutputsDir);
+
+  BinFormatVisitor visitor;
+  // Parse and process the input file.
+  EXPECT_TRUE(visitor
+                  .Process(input_files, kRiscVDecoderName, kRiscVBaseName,
+                           paths_, output_dir)
+                  .ok());
+  // Verify that the decoder files _decoder.{.h,.cc} files were generated.
+  EXPECT_TRUE(FileExists(
+      absl::StrCat(output_dir, "/", kRiscVBaseName, "_bin_decoder.h")));
+
+  EXPECT_TRUE(FileExists(
+      absl::StrCat(output_dir, "/", kRiscVBaseName, "_bin_decoder.cc")));
+}
+
+}  // namespace
diff --git a/mpact/sim/decoder/test/combined_decoder_test.cc b/mpact/sim/decoder/test/combined_decoder_test.cc
new file mode 100644
index 0000000..6c20fd8
--- /dev/null
+++ b/mpact/sim/decoder/test/combined_decoder_test.cc
@@ -0,0 +1,25 @@
+// 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 "googlemock/include/gmock/gmock.h"
+#include "googletest/include/gtest/gtest.h"
+
+namespace {
+
+// Trivial case that succeeds as long as the generated code compiles, including
+// base classes, as this test file build dependency includes the .isa grammar
+// file. See the BUILD file for details.
+TEST(CombinedDecoderTest, Trivial) { EXPECT_TRUE(true); }
+
+}  // namespace
diff --git a/mpact/sim/decoder/test/instruction_encoding_test.cc b/mpact/sim/decoder/test/instruction_encoding_test.cc
new file mode 100644
index 0000000..dace875
--- /dev/null
+++ b/mpact/sim/decoder/test/instruction_encoding_test.cc
@@ -0,0 +1,220 @@
+// 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 "mpact/sim/decoder/instruction_encoding.h"
+
+#include <string>
+
+#include "absl/status/status.h"
+#include "googlemock/include/gmock/gmock.h"
+#include "googletest/include/gtest/gtest.h"
+#include "mpact/sim/decoder/bin_encoding_info.h"
+#include "mpact/sim/decoder/bin_format_visitor.h"
+#include "mpact/sim/decoder/decoder_error_listener.h"
+#include "mpact/sim/decoder/format.h"
+
+namespace {
+
+// This file contains the unit tests for the InstructionEncoding class.
+
+using ::mpact::sim::decoder::DecoderErrorListener;
+using ::mpact::sim::decoder::bin_format::BinEncodingInfo;
+using ::mpact::sim::decoder::bin_format::ConstraintType;
+using ::mpact::sim::decoder::bin_format::Format;
+using ::mpact::sim::decoder::bin_format::InstructionEncoding;
+
+constexpr char kITypeEncodingName[] = "i_test_encoding";
+
+constexpr int kFunc3Value = 0b101;
+constexpr int kFunc3Mask = 0b111'00000'000'0000;
+
+class InstructionEncodingTest : public ::testing::Test {
+ protected:
+  InstructionEncodingTest() {
+    error_listener_ = new DecoderErrorListener();
+    encoding_info_ = new BinEncodingInfo("OpcodeEnumName", error_listener_);
+    i_type_fmt_ = new Format("IType", 32, encoding_info_);
+    (void)i_type_fmt_->AddField("imm12", /*is_signed*/ true, 12);
+    (void)i_type_fmt_->AddField("rs1", /*is_signed*/ false, 5);
+    (void)i_type_fmt_->AddField("func3", /*is_signed*/ false, 3);
+    (void)i_type_fmt_->AddField("rd", /*is_signed*/ false, 5);
+    (void)i_type_fmt_->AddField("opcode", /*is_signed*/ false, 7);
+    (void)i_type_fmt_->AddFieldOverlay("uspecial", /*is_signed*/ false, 10);
+    auto *overlay = i_type_fmt_->GetOverlay("uspecial");
+    (void)overlay->AddFieldReference("rs1");
+    (void)overlay->AddFieldReference("rd");
+    (void)i_type_fmt_->AddFieldOverlay("sspecial", /*is_signed*/ true, 10);
+    overlay = i_type_fmt_->GetOverlay("sspecial");
+    (void)overlay->AddFieldReference("rs1");
+    (void)overlay->AddFieldReference("rd");
+    (void)i_type_fmt_->ComputeAndCheckFormatWidth();
+    i_type_ = new InstructionEncoding(kITypeEncodingName, i_type_fmt_);
+  }
+
+  ~InstructionEncodingTest() override {
+    delete error_listener_;
+    delete i_type_fmt_;
+    delete encoding_info_;
+    delete i_type_;
+  }
+
+  DecoderErrorListener *error_listener_;
+  BinEncodingInfo *encoding_info_;
+  Format *i_type_fmt_;
+  InstructionEncoding *i_type_;
+};
+
+TEST_F(InstructionEncodingTest, Basic) {
+  EXPECT_EQ(i_type_->name(), kITypeEncodingName);
+  EXPECT_EQ(i_type_->GetValue(), 0);
+  EXPECT_EQ(i_type_->GetMask(), 0);
+  EXPECT_EQ(i_type_->GetCombinedMask(), 0);
+  EXPECT_TRUE(i_type_->equal_constraints().empty());
+  EXPECT_TRUE(i_type_->equal_extracted_constraints().empty());
+  EXPECT_TRUE(i_type_->other_constraints().empty());
+}
+
+TEST_F(InstructionEncodingTest, BadConstraintName) {
+  // Wrong field names.
+  auto status = i_type_->AddEqualConstraint("NotAName", 0);
+  // Verify error message, and that nothing has changed.
+  EXPECT_TRUE(absl::IsNotFound(status));
+  EXPECT_EQ(i_type_->GetValue(), 0);
+  EXPECT_EQ(i_type_->GetMask(), 0);
+  EXPECT_EQ(i_type_->GetCombinedMask(), 0);
+  EXPECT_TRUE(i_type_->equal_constraints().empty());
+  EXPECT_TRUE(i_type_->equal_extracted_constraints().empty());
+  EXPECT_TRUE(i_type_->other_constraints().empty());
+
+  // Check for other constraint type.
+  status = i_type_->AddOtherConstraint(ConstraintType::kNe, "NotAName", 0);
+  EXPECT_TRUE(absl::IsNotFound(status));
+  EXPECT_EQ(i_type_->GetValue(), 0);
+  EXPECT_EQ(i_type_->GetMask(), 0);
+  EXPECT_EQ(i_type_->GetCombinedMask(), 0);
+  EXPECT_TRUE(i_type_->equal_constraints().empty());
+  EXPECT_TRUE(i_type_->equal_extracted_constraints().empty());
+  EXPECT_TRUE(i_type_->other_constraints().empty());
+}
+
+TEST_F(InstructionEncodingTest, OutOfRangeUnsignedField) {
+  // Correct field name, but value out of range.
+  auto status = i_type_->AddEqualConstraint("func3", 8);
+  EXPECT_TRUE(absl::IsOutOfRange(status));
+  status = i_type_->AddEqualConstraint("func3", -5);
+  EXPECT_TRUE(absl::IsOutOfRange(status));
+  status = i_type_->AddOtherConstraint(ConstraintType::kLt, "func3", 8);
+  EXPECT_TRUE(absl::IsOutOfRange(status));
+  status = i_type_->AddOtherConstraint(ConstraintType::kLe, "func3", -5);
+  EXPECT_TRUE(absl::IsOutOfRange(status));
+}
+
+TEST_F(InstructionEncodingTest, OutOfRangeSignedField) {
+  auto status = i_type_->AddEqualConstraint("imm12", 1 << 11);
+  EXPECT_TRUE(absl::IsOutOfRange(status));
+  status = i_type_->AddEqualConstraint("imm12", -(1 << 11) - 1);
+  EXPECT_TRUE(absl::IsOutOfRange(status));
+  status = i_type_->AddOtherConstraint(ConstraintType::kNe, "imm12", 1 << 11);
+  EXPECT_TRUE(absl::IsOutOfRange(status));
+  status =
+      i_type_->AddOtherConstraint(ConstraintType::kNe, "imm12", -(1 << 11) - 1);
+  EXPECT_TRUE(absl::IsOutOfRange(status));
+}
+
+TEST_F(InstructionEncodingTest, OutOfRangeUnsignedOverlay) {
+  // Correct field name, but value out of range.
+  auto status = i_type_->AddEqualConstraint("uspecial", 1024);
+  EXPECT_TRUE(absl::IsOutOfRange(status));
+  status = i_type_->AddEqualConstraint("uspecial", -5);
+  EXPECT_TRUE(absl::IsOutOfRange(status));
+  status = i_type_->AddOtherConstraint(ConstraintType::kNe, "uspecial", 1024);
+  EXPECT_TRUE(absl::IsOutOfRange(status));
+  status = i_type_->AddOtherConstraint(ConstraintType::kNe, "uspecial", -5);
+  EXPECT_TRUE(absl::IsOutOfRange(status));
+}
+
+TEST_F(InstructionEncodingTest, OutOfRangeSignedOverlay) {
+  auto status = i_type_->AddEqualConstraint("sspecial", 1 << 10);
+  EXPECT_TRUE(absl::IsOutOfRange(status));
+  status = i_type_->AddEqualConstraint("sspecial", -(1 << 10) - 1);
+  EXPECT_TRUE(absl::IsOutOfRange(status));
+  status =
+      i_type_->AddOtherConstraint(ConstraintType::kNe, "sspecial", 1 << 10);
+  EXPECT_TRUE(absl::IsOutOfRange(status));
+  status = i_type_->AddOtherConstraint(ConstraintType::kNe, "sspecial",
+                                       -(1 << 10) - 1);
+  EXPECT_TRUE(absl::IsOutOfRange(status));
+}
+
+TEST_F(InstructionEncodingTest, IllegalSignedConstraints) {
+  // Field.
+  auto status = i_type_->AddOtherConstraint(ConstraintType::kLt, "imm12", 5);
+  EXPECT_TRUE(absl::IsInvalidArgument(status));
+  status = i_type_->AddOtherConstraint(ConstraintType::kLe, "imm12", 5);
+  EXPECT_TRUE(absl::IsInvalidArgument(status));
+  status = i_type_->AddOtherConstraint(ConstraintType::kGt, "imm12", 5);
+  EXPECT_TRUE(absl::IsInvalidArgument(status));
+  status = i_type_->AddOtherConstraint(ConstraintType::kGe, "imm12", 5);
+  EXPECT_TRUE(absl::IsInvalidArgument(status));
+  // Overlay.
+  status = i_type_->AddOtherConstraint(ConstraintType::kLt, "sspecial", 5);
+  EXPECT_TRUE(absl::IsInvalidArgument(status));
+  status = i_type_->AddOtherConstraint(ConstraintType::kLe, "sspecial", 5);
+  EXPECT_TRUE(absl::IsInvalidArgument(status));
+  status = i_type_->AddOtherConstraint(ConstraintType::kGt, "sspecial", 5);
+  EXPECT_TRUE(absl::IsInvalidArgument(status));
+  status = i_type_->AddOtherConstraint(ConstraintType::kGe, "sspecial", 5);
+  EXPECT_TRUE(absl::IsInvalidArgument(status));
+}
+
+TEST_F(InstructionEncodingTest, AddEqualUnsignedConstraint) {
+  auto status = i_type_->AddEqualConstraint("func3", kFunc3Value);
+  EXPECT_TRUE(status.ok());
+  EXPECT_EQ(i_type_->GetValue(), kFunc3Value << 12);
+  EXPECT_EQ(i_type_->GetMask(), kFunc3Mask);
+  EXPECT_EQ(i_type_->GetCombinedMask(), kFunc3Mask);
+  EXPECT_EQ(i_type_->equal_constraints().size(), 1);
+  auto *constraint = i_type_->equal_constraints()[0];
+  EXPECT_EQ(constraint->type, ConstraintType::kEq);
+  EXPECT_EQ(constraint->field, i_type_fmt_->GetField("func3"));
+  EXPECT_EQ(constraint->value, kFunc3Value);
+  EXPECT_EQ(constraint->overlay, nullptr);
+  EXPECT_EQ(constraint->can_ignore, false);
+
+  // Other constraints are unaffected.
+  EXPECT_TRUE(i_type_->equal_extracted_constraints().empty());
+  EXPECT_TRUE(i_type_->other_constraints().empty());
+}
+
+TEST_F(InstructionEncodingTest, AddOtherConstraints) {
+  auto status =
+      i_type_->AddOtherConstraint(ConstraintType::kGe, "func3", kFunc3Value);
+  EXPECT_TRUE(status.ok());
+  EXPECT_EQ(i_type_->GetValue(), 0);
+  EXPECT_EQ(i_type_->GetMask(), 0);
+  EXPECT_EQ(i_type_->GetCombinedMask(), kFunc3Mask);
+  EXPECT_EQ(i_type_->other_constraints().size(), 1);
+  auto *constraint = i_type_->other_constraints()[0];
+  EXPECT_EQ(constraint->type, ConstraintType::kGe);
+  EXPECT_EQ(constraint->field, i_type_fmt_->GetField("func3"));
+  EXPECT_EQ(constraint->value, kFunc3Value);
+  EXPECT_EQ(constraint->overlay, nullptr);
+  EXPECT_EQ(constraint->can_ignore, false);
+
+  // Other constraints are unaffected.
+  EXPECT_TRUE(i_type_->equal_extracted_constraints().empty());
+  EXPECT_TRUE(i_type_->equal_constraints().empty());
+}
+
+}  // namespace
diff --git a/mpact/sim/decoder/test/instruction_set_visitor_test.cc b/mpact/sim/decoder/test/instruction_set_visitor_test.cc
index f7fe3d4..65a32f3 100644
--- a/mpact/sim/decoder/test/instruction_set_visitor_test.cc
+++ b/mpact/sim/decoder/test/instruction_set_visitor_test.cc
@@ -59,15 +59,15 @@
 // An empty file should fail.
 TEST_F(InstructionSetParserTest, NullFileParsing) {
   // Set up input and output file paths.
-  const std::string input_file =
-      absl::StrCat(kDepotPath, "testfiles/", kEmptyBaseName, ".isa");
-  ASSERT_TRUE(FileExists(input_file));
+  std::vector<std::string> input_files = {
+      absl::StrCat(kDepotPath, "testfiles/", kEmptyBaseName, ".isa")};
+  ASSERT_TRUE(FileExists(input_files[0]));
   std::string output_dir = OutputDir();
 
   InstructionSetVisitor visitor;
   // Parse and process the input file.
   EXPECT_FALSE(visitor
-                   .Process(input_file, kEmptyBaseName, kEmptyIsaName, paths_,
+                   .Process(input_files, kEmptyBaseName, kEmptyIsaName, paths_,
                             output_dir)
                    .ok());
 }
@@ -75,30 +75,30 @@
 // Make sure recursive includes cause a failure.
 TEST_F(InstructionSetParserTest, RecursiveInclude) {
   // Set up input and output file paths.
-  const std::string input_file =
-      absl::StrCat(kDepotPath, "testfiles/", kRecursiveExampleBaseName, ".isa");
-  ASSERT_TRUE(FileExists(input_file));
+  std::vector<std::string> input_files = {absl::StrCat(
+      kDepotPath, "testfiles/", kRecursiveExampleBaseName, ".isa")};
+  ASSERT_TRUE(FileExists(input_files[0]));
   std::string output_dir = OutputDir();
 
   InstructionSetVisitor visitor;
   EXPECT_FALSE(visitor
-                   .Process(input_file, kRecursiveExampleBaseName,
+                   .Process(input_files, kRecursiveExampleBaseName,
                             kExampleIsaName, paths_, output_dir)
                    .ok());
-}
+}  // namespace
 
 // Make sure the visitor can read and parse the input file.
 TEST_F(InstructionSetParserTest, BasicParsing) {
   // Set up input and output file paths.
-  const std::string input_file =
-      absl::StrCat(kDepotPath, "testfiles/", kExampleBaseName, ".isa");
-  ASSERT_TRUE(FileExists(input_file));
+  std::vector<std::string> input_files = {
+      absl::StrCat(kDepotPath, "testfiles/", kExampleBaseName, ".isa")};
+  ASSERT_TRUE(FileExists(input_files[0]));
   std::string output_dir = getenv(kTestUndeclaredOutputsDir);
 
   InstructionSetVisitor visitor;
   // Parse and process the input file.
   EXPECT_TRUE(visitor
-                  .Process(input_file, kExampleBaseName, kExampleIsaName,
+                  .Process(input_files, kExampleBaseName, kExampleIsaName,
                            paths_, output_dir)
                   .ok());
   // Verify that the decoder files _decoder.{.h,.cc} files were generated.
diff --git a/mpact/sim/decoder/test/resource_test.cc b/mpact/sim/decoder/test/resource_test.cc
new file mode 100644
index 0000000..a8c8159
--- /dev/null
+++ b/mpact/sim/decoder/test/resource_test.cc
@@ -0,0 +1,65 @@
+#include "mpact/sim/decoder/resource.h"
+
+#include "absl/status/statusor.h"
+#include "googlemock/include/gmock/gmock.h"
+#include "googletest/include/gtest/gtest.h"
+
+namespace {
+
+// This file contains tests for Resource and ResourceFactory.
+
+using ::mpact::sim::machine_description::instruction_set::ResourceFactory;
+
+constexpr char kResource1PascalName[] = "Resource1Name";
+constexpr char kResource1Name[] = "resource_1_name";
+constexpr char kResource2Name[] = "resource_2_name";
+
+class ResourceTest : public ::testing::Test {
+ protected:
+  ResourceTest() {}
+  ~ResourceTest() override {}
+
+  ResourceFactory factory_;
+};
+
+// Test that ResourceFactory works as expected.
+TEST_F(ResourceTest, Factory) {
+  auto result = factory_.CreateResource(kResource1Name);
+  EXPECT_TRUE(result.status().ok());
+  auto result2 = factory_.CreateResource(kResource1Name);
+  EXPECT_TRUE(absl::IsAlreadyExists(result2.status()));
+  auto *resource1 = factory_.GetOrInsertResource(kResource1Name);
+  EXPECT_EQ(result.value(), resource1);
+  auto *resource2 = factory_.GetOrInsertResource(kResource2Name);
+  EXPECT_NE(resource2, nullptr);
+  EXPECT_NE(resource2, resource1);
+  auto result3 = factory_.CreateResource(kResource2Name);
+  EXPECT_TRUE(absl::IsAlreadyExists(result3.status()));
+
+  EXPECT_EQ(factory_.resource_map().find(kResource1Name)->second, resource1);
+  EXPECT_EQ(factory_.resource_map().find(kResource2Name)->second, resource2);
+}
+
+// Test initial state of a new resource.
+TEST_F(ResourceTest, ResourceInitial) {
+  auto *resource = factory_.GetOrInsertResource(kResource1Name);
+  EXPECT_TRUE(resource->is_simple());
+  EXPECT_FALSE(resource->is_multi_valued());
+  EXPECT_EQ(resource->name(), kResource1Name);
+  EXPECT_EQ(resource->pascal_name(), kResource1PascalName);
+}
+
+// Test resource setters.
+TEST_F(ResourceTest, ResourceSetters) {
+  auto *resource = factory_.GetOrInsertResource(kResource1Name);
+  resource->set_is_simple(false);
+  resource->set_is_multi_valued(true);
+  EXPECT_FALSE(resource->is_simple());
+  EXPECT_TRUE(resource->is_multi_valued());
+  resource->set_is_simple(true);
+  resource->set_is_multi_valued(false);
+  EXPECT_TRUE(resource->is_simple());
+  EXPECT_FALSE(resource->is_multi_valued());
+}
+
+}  // namespace
diff --git a/mpact/sim/decoder/test/testfiles/empty_file.bin_fmt b/mpact/sim/decoder/test/testfiles/empty_file.bin_fmt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/mpact/sim/decoder/test/testfiles/empty_file.bin_fmt
diff --git a/mpact/sim/decoder/test/testfiles/part1.isa b/mpact/sim/decoder/test/testfiles/part1.isa
new file mode 100644
index 0000000..3bfc3f0
--- /dev/null
+++ b/mpact/sim/decoder/test/testfiles/part1.isa
@@ -0,0 +1,109 @@
+// 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.
+
+// Define an isa with two bundles.
+isa Example {
+  namespace sim::example::isa;
+  bundles {
+    bundle_a;
+    bundle_b;
+  }
+}
+
+int const1 = 1;
+int my_const = const1 + 2;
+
+// Try some include files.
+#include "mpact/sim/decoder/test/testfiles/include/empty_include.isa"
+#include "mpact/sim/decoder/test/testfiles/include/bundle_a.isa"
+
+template<int base, int mult>
+slot a_side_ops {
+  int base_plus_1 = base + 1;
+  int mult_plus_2 = mult + my_const;
+  default attributes = { one, two = 3, three = base, four = base_plus_1, five = 0};
+  opcodes {
+    vctsf{(pred : sy : dest(base_plus_1))};
+    cvtfs{(pred : sy : dest(base + 1))};
+    adds{(pred : sy, sx : dest(base))},
+      attributes: {five = 1, six};
+    addf{(pred : sy, sx : dest(base))};
+    subf{(pred : sy, sx : dest(base))};
+    mulf{(pred : sy, sx : dest(mult_plus_2))};
+    mulu{(pred : sy, sx : dest(mult + 1))};
+  }
+}
+
+slot a_side  {
+  opcodes {
+    nop{()};
+    delay{(pred : const)};
+    settag{()};
+    fence{()};
+  }
+}
+
+slot a_side_0 : a_side, a_side_ops<2, 2> {
+  default opcode =
+    semfunc: "[](Instruction *) {}";
+  opcodes {
+    settag = delete;
+    addf = delete;
+    subf = delete;
+    br_abs{()};
+    br_rel{()};
+    br_ind{()};
+    call_abs{()};
+    call_rel{()};
+    call_ind{()};
+  }
+}
+
+slot a_side_1 : a_side, a_side_ops<1, 4> {
+  default opcode =
+    semfunc: "[](Instruction *) {}";
+  opcodes {
+    ld{(pred : yop : ),(: : dest)},
+      disasm:"%dest = sld %pred [smem:%yop]";
+    ld_offset{(pred : xop, yop : dest(abs(-2)))};
+    st{(pred)};
+    mulf = delete;
+    mulu = delete;
+  }
+}
+
+slot other {
+  default opcode =
+    semfunc: "[](Instruction *) {}";
+}
+
+slot b_side_alu[4] {
+  default opcode =
+    semfunc: "[](Instruction *) {}";
+}
+
+slot b_side_store {
+  default opcode =
+    semfunc: "[](Instruction *) {}";
+}
+
+slot b_side_load[2] {
+  default opcode =
+    semfunc: "[](Instruction *) {}";
+}
+
+slot b_side_load_2 {
+  default opcode =
+    semfunc: "[](Instruction *) {}";
+}
diff --git a/mpact/sim/decoder/test/testfiles/riscv32_top.bin_fmt b/mpact/sim/decoder/test/testfiles/riscv32_top.bin_fmt
new file mode 100644
index 0000000..6eb694d
--- /dev/null
+++ b/mpact/sim/decoder/test/testfiles/riscv32_top.bin_fmt
@@ -0,0 +1,24 @@
+// 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.
+
+// RiscV 32 bit G instruction decoder.
+decoder RiscV32G {
+  namespace mpact::sim::riscv::encoding;
+  opcode_enum = "isa32::OpcodeEnum";
+  includes {
+    #include "third_party/mpact_riscv/riscv32g_decoder.h"
+  }
+  RiscVGInst32;
+  RiscVCInst16;
+};
diff --git a/mpact/sim/decoder/test/testfiles/riscv32c.bin_fmt b/mpact/sim/decoder/test/testfiles/riscv32c.bin_fmt
new file mode 100644
index 0000000..d7aef71
--- /dev/null
+++ b/mpact/sim/decoder/test/testfiles/riscv32c.bin_fmt
@@ -0,0 +1,176 @@
+// 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.
+
+// Compact instruction formats.
+
+format Inst16Format[16] {
+  fields:
+    unsigned func3[3];
+    unsigned bits[11];
+    unsigned op[2];
+};
+
+format CSS[16] : Inst16Format {
+  fields:
+    unsigned func3[3];
+    unsigned imm6[6];
+    unsigned rs2[5];
+    unsigned op[2];
+  overlays:
+    unsigned css_imm_w[8] = imm6[1..0], imm6[5..2], 0b00;
+    unsigned css_imm_d[9] = imm6[2..0], imm6[5..3], 0b000;
+};
+
+format CL[16] : Inst16Format {
+  fields:
+    unsigned func3[3];
+    unsigned imm3[3];
+    unsigned rs1p[3];
+    unsigned imm2[2];
+    unsigned rdp[3];
+    unsigned op[2];
+  overlays:
+    unsigned cl_rs1[5] = 0b01, rs1p;
+    unsigned cl_rd[5] = 0b01, rdp;
+    unsigned cl_imm_w[7] = imm2[0], imm3, imm2[1], 0b00;
+    unsigned cl_imm_d[8] = imm2, imm3, 0b000;
+};
+
+format CS[16] : Inst16Format {
+  fields:
+    unsigned func3[3];
+    unsigned imm3[3];
+    unsigned rs1p[3];
+    unsigned imm2[2];
+    unsigned rs2p[3];
+    unsigned op[2];
+  overlays:
+    unsigned cs_rs1[5] = 0b01, rs1p;
+    unsigned cs_rs2[5] = 0b01, rs2p;
+    unsigned cs_imm_w[7] = imm2[0], imm3, imm2[1], 0b00;
+    unsigned cs_imm_d[8] = imm2, imm3, 0b000;
+};
+
+format CJ[16] : Inst16Format {
+  fields:
+    unsigned func3[3];
+    unsigned imm11[11];
+    unsigned op[2];
+  overlays:
+    signed jimm[12] = imm11[10, 6, 8..7, 4, 5, 0, 9, 3..1], 0b0;
+};
+
+format CR[16] : Inst16Format {
+  fields:
+    unsigned func4[4];
+    unsigned rs1[5];
+    unsigned rs2[5];
+    unsigned op[2];
+};
+
+format CB[16] : Inst16Format {
+  fields:
+    unsigned func3[3];
+    unsigned imm3[3];
+    unsigned rs1p[3];
+    unsigned imm5[5];
+    unsigned op[2];
+  overlays:
+    unsigned func2[2] = [11, 10];
+    unsigned func5[5] = [12..10, 6..5];
+    unsigned shamt[6] = [12, 6..2];
+    unsigned rs2p[3] = [4..2];
+    unsigned rs2[5] = 0b10, [4..2];
+    signed bimm[9] = imm3[2], imm5[4..3, 0], imm3[1..0], imm5[2..1], 0b0;
+};
+
+format CI[16] : Inst16Format {
+  fields:
+    unsigned func3[3];
+    unsigned imm1[1];
+    unsigned rs1[5];
+    unsigned imm5[5];
+    unsigned op[2];
+  overlays:
+    unsigned rd[5] = rs1;
+    signed imm6[6] = imm1, imm5;
+    unsigned uimm6[6] = imm1, imm5;
+    signed imm18[18] = imm1, imm5, 0b0000'0000'0000;
+    signed ci_imm10[10] = imm1, imm5[2..1, 3, 0, 4], 0b0000;
+    unsigned ci_imm_w[8] = imm5[1..0], imm1, imm5[4..2], 0b00;
+    unsigned ci_imm_d[9] = imm5[2..0], imm1, imm5[4..3], 0b000;
+};
+
+format CIW[16] : Inst16Format {
+  fields:
+    unsigned func3[3];
+    unsigned imm8[8];
+    unsigned rdp[3];
+    unsigned op[2];
+  overlays:
+    unsigned rd[5] = 0b01, rdp;
+    unsigned ciw_imm10[10] = imm8[5..2, 7..6, 0, 1], 0b00;
+};
+
+format CA[16] : Inst16Format {
+  fields:
+    unsigned func6[6];
+    unsigned rs1p[3];
+    unsigned func2[2];
+    unsigned fs2p[3];
+    unsigned op[2];
+  overlays:
+    unsigned rs1[5] = 0b01, rs1p;
+    unsigned rs2[5] = 0b01, fs2p;
+    unsigned rd[5] = 0b01, rs1p;
+};
+
+// Compact instruction encodings.
+instruction group RiscVCInst16[16] : Inst16Format {
+  caddi4spn : CIW: func3 == 0b000, op == 0b00, imm8 != 0;
+  cfld      : CL : func3 == 0b001, op == 0b00;
+  clw       : CL : func3 == 0b010, op == 0b00;
+  cflw      : CL : func3 == 0b011, op == 0b00;
+  cfsd      : CS : func3 == 0b101, op == 0b00;
+  csw       : CS : func3 == 0b110, op == 0b00;
+  cfsw      : CS : func3 == 0b111, op == 0b00;
+  cnop      : CI : func3 == 0b000, imm1 == 0, rs1 == 0, imm5 == 0, op == 0b01;
+  caddi     : CI : func3 == 0b000, imm6 != 0, rd != 0, op == 0b01;
+  cjal      : CJ : func3 == 0b001, op == 0b01;
+  cli       : CI : func3 == 0b010, rd != 0, op == 0b01;
+  caddi16sp : CI : func3 == 0b011, rd == 2, op == 0b01;
+  clui      : CI : func3 == 0b011, rd != 0, rd != 2, op == 0b01;
+  csrli     : CB : func3 == 0b100, imm3 == 0b000, op == 0b01;
+  csrai     : CB : func3 == 0b100, imm3 == 0b001, op == 0b01;
+  candi     : CB : func3 == 0b100, func2 == 0b10, op == 0b01;
+  csub      : CA : func6 == 0b100'011, func2 == 0b00, op == 0b01;
+  cxor      : CA : func6 == 0b100'011, func2 == 0b01, op == 0b01;
+  cor       : CA : func6 == 0b100'011, func2 == 0b10, op == 0b01;
+  cand      : CA : func6 == 0b100'011, func2 == 0b11, op == 0b01;
+  cj        : CJ : func3 == 0b101, op == 0b01;
+  cbeqz     : CB : func3 == 0b110, op == 0b01;
+  cbnez     : CB : func3 == 0b111, op == 0b01;
+  cslli     : CI : func3 == 0b000, imm1 == 0, rs1 != 0, op == 0b10;
+  cfldsp    : CI : func3 == 0b001, op == 0b10;
+  clwsp     : CI : func3 == 0b010, rd != 0, op == 0b10;
+  cflwsp    : CI : func3 == 0b011, op == 0b10;
+  cjr       : CR : func4 == 0b1000, rs1 != 0, rs2 == 0, op == 0b10;
+  cmv       : CR : func4 == 0b1000, rs1 != 0, rs2 != 0, op == 0b10;
+  cebreak   : CR : func4 == 0b1001, rs1 == 0, rs2 == 0, op == 0b10;
+  cjalr     : CR : func4 == 0b1001, rs1 != 0, rs2 == 0, op == 0b10;
+  cadd      : CR : func4 == 0b1001, rs1 != 0, rs2 != 0, op == 0b10;
+  cfsdsp    : CSS: func3 == 0b101, op == 0b10;
+  cswsp     : CSS: func3 == 0b110, op == 0b10;
+  cfswsp    : CSS: func3 == 0b111, op == 0b10;
+};
diff --git a/mpact/sim/decoder/test/testfiles/riscv32g.bin_fmt b/mpact/sim/decoder/test/testfiles/riscv32g.bin_fmt
new file mode 100644
index 0000000..b05875a
--- /dev/null
+++ b/mpact/sim/decoder/test/testfiles/riscv32g.bin_fmt
@@ -0,0 +1,268 @@
+// 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.
+
+// RiscV 32 bit instructions.
+format Inst32Format[32] {
+  fields:
+    unsigned bits[25];
+    unsigned opcode[7];
+};
+
+format RType[32] : Inst32Format {
+  fields:
+    unsigned func7[7];
+    unsigned rs2[5];
+    unsigned rs1[5];
+    unsigned func3[3];
+    unsigned rd[5];
+    unsigned opcode[7];
+  overlays:
+    unsigned r_uimm5[5] = rs2;
+};
+
+format R4Type[32] : Inst32Format {
+  fields:
+    unsigned rs3[5];
+    unsigned func2[2];
+    unsigned rs2[5];
+    unsigned rs1[5];
+    unsigned func3[3];
+    unsigned rd[5];
+    unsigned opcode[7];
+};
+
+format IType[32] : Inst32Format {
+  fields:
+    signed imm12[12];
+    unsigned rs1[5];
+    unsigned func3[3];
+    unsigned rd[5];
+    unsigned opcode[7];
+  overlays:
+    unsigned u_imm12[12] = imm12;
+    unsigned i_uimm5[5] = rs1;
+};
+
+format SType[32] : Inst32Format {
+  fields:
+    unsigned imm7[7];
+    unsigned rs2[5];
+    unsigned rs1[5];
+    unsigned func3[3];
+    unsigned imm5[5];
+    unsigned opcode[7];
+  overlays:
+    signed s_imm[12] = imm7, imm5;
+};
+
+format BType[32] : Inst32Format {
+  fields:
+    unsigned imm7[7];
+    unsigned rs2[5];
+    unsigned rs1[5];
+    unsigned func3[3];
+    unsigned imm5[5];
+    unsigned opcode[7];
+  overlays:
+    signed b_imm[13] = imm7[6], imm5[0], imm7[5..0], imm5[4..1], 0b0;
+};
+
+format UType[32] : Inst32Format {
+  fields:
+    unsigned imm20[20];
+    unsigned rd[5];
+    unsigned opcode[7];
+  overlays:
+    unsigned u_imm[32] = imm20, 0b0000'0000'0000;
+};
+
+format JType[32] : Inst32Format {
+  fields:
+    unsigned imm20[20];
+    unsigned rd[5];
+    unsigned opcode[7];
+  overlays:
+    signed j_imm[21] = imm20[19, 7..0, 8, 18..9], 0b0;
+};
+
+format Fence[32] : Inst32Format {
+  fields:
+    unsigned fm[4];
+    unsigned pred[4];
+    unsigned succ[4];
+    unsigned rs1[5];
+    unsigned func3[3];
+    unsigned rd[5];
+    unsigned opcode[7];
+};
+
+format AType[32] : Inst32Format {
+  fields:
+    unsigned func5[5];
+    unsigned aq[1];
+    unsigned rl[1];
+    unsigned rs2[5];
+    unsigned rs1[5];
+    unsigned func3[3];
+    unsigned rd[5];
+    unsigned opcode[7];
+};
+
+instruction group RiscVGInst32[32] : Inst32Format {
+  lui    : UType  : opcode == 0b011'0111;
+  auipc  : UType  : opcode == 0b001'0111;
+  jal    : JType  : rd != 0, opcode == 0b110'1111;
+  j      : JType  : rd == 0, opcode == 0b110'1111;
+  jalr   : IType  : rd != 0, func3 == 0b000, opcode == 0b110'0111;
+  jr     : IType  : rd == 0, func3 == 0b000, opcode == 0b110'0111;
+  beq    : BType  : func3 == 0b000, opcode == 0b110'0011;
+  bne    : BType  : func3 == 0b001, opcode == 0b110'0011;
+  blt    : BType  : func3 == 0b100, opcode == 0b110'0011;
+  bge    : BType  : func3 == 0b101, opcode == 0b110'0011;
+  bltu   : BType  : func3 == 0b110, opcode == 0b110'0011;
+  bgeu   : BType  : func3 == 0b111, opcode == 0b110'0011;
+  lb     : BType  : func3 == 0b000, opcode == 0b000'0011;
+  lh     : BType  : func3 == 0b001, opcode == 0b000'0011;
+  lw     : BType  : func3 == 0b010, opcode == 0b000'0011;
+  lbu    : BType  : func3 == 0b100, opcode == 0b000'0011;
+  lhu    : BType  : func3 == 0b101, opcode == 0b000'0011;
+  sb     : SType  : func3 == 0b000, opcode == 0b010'0011;
+  sh     : SType  : func3 == 0b001, opcode == 0b010'0011;
+  sw     : SType  : func3 == 0b010, opcode == 0b010'0011;
+  addi   : IType  : func3 == 0b000, opcode == 0b001'0011;
+  slti   : IType  : func3 == 0b010, opcode == 0b001'0011;
+  sltiu  : IType  : func3 == 0b011, opcode == 0b001'0011;
+  xori   : IType  : func3 == 0b100, opcode == 0b001'0011;
+  ori    : IType  : func3 == 0b110, opcode == 0b001'0011;
+  andi   : IType  : func3 == 0b111, opcode == 0b001'0011;
+  slli   : RType  : func7 == 0b000'0000, func3==0b001, opcode == 0b001'0011;
+  srli   : RType  : func7 == 0b000'0000, func3==0b101, opcode == 0b001'0011;
+  srai   : RType  : func7 == 0b010'0000, func3==0b101, opcode == 0b001'0011;
+  add    : RType  : func7 == 0b000'0000, func3==0b000, opcode == 0b011'0011;
+  sub    : RType  : func7 == 0b010'0000, func3==0b000, opcode == 0b011'0011;
+  sll    : RType  : func7 == 0b000'0000, func3==0b001, opcode == 0b011'0011;
+  slt    : RType  : func7 == 0b000'0000, func3==0b010, opcode == 0b011'0011;
+  sltu   : RType  : func7 == 0b000'0000, func3==0b011, opcode == 0b011'0011;
+  xor    : RType  : func7 == 0b000'0000, func3==0b100, opcode == 0b011'0011;
+  srl    : RType  : func7 == 0b000'0000, func3==0b101, opcode == 0b011'0011;
+  sra    : RType  : func7 == 0b010'0000, func3==0b101, opcode == 0b011'0011;
+  or     : RType  : func7 == 0b000'0000, func3==0b110, opcode == 0b011'0011;
+  and    : RType  : func7 == 0b000'0000, func3==0b111, opcode == 0b011'0011;
+  fence  : Fence  : func3 == 0b000, opcode == 0b000'1111;
+  ecall  : Inst32Format : bits == 0b0000'0000'0000'00000'000'00000, opcode == 0b111'0011;
+  ebreak : Inst32Format : bits == 0b0000'0000'0001'00000'000'00000, opcode == 0b111'0011;
+  // RiscV32 Instruction fence.
+  fencei : IType  : func3 == 001, opcode == 0b000'1111;
+  // RiscV32 multiply divide.
+  mul    : RType  : func7 == 0b000'0001, func3 == 0b000, opcode == 0b011'0011;
+  mulh   : RType  : func7 == 0b000'0001, func3 == 0b001, opcode == 0b011'0011;
+  mulhsu : RType  : func7 == 0b000'0001, func3 == 0b010, opcode == 0b011'0011;
+  mulhu  : RType  : func7 == 0b000'0001, func3 == 0b011, opcode == 0b011'0011;
+  div    : RType  : func7 == 0b000'0001, func3 == 0b100, opcode == 0b011'0011;
+  divu   : RType  : func7 == 0b000'0001, func3 == 0b101, opcode == 0b011'0011;
+  rem    : RType  : func7 == 0b000'0001, func3 == 0b110, opcode == 0b011'0011;
+  remu   : RType  : func7 == 0b000'0001, func3 == 0b111, opcode == 0b011'0011;
+  // RiscV32 atomic instructions.
+  lrw       : AType  : func5 == 0b0'0010, rs2 == 0, func3 == 0b010, opcode == 0b010'1111;
+  scw       : AType  : func5 == 0b0'0011, func3 == 0b010, opcode == 0b010'1111;
+  amoswapw : AType  : func5 == 0b0'0001,  func3 == 0b010, opcode == 0b010'1111;
+  amoaddw  : AType  : func5 == 0b0'0000, func3 == 0b010, opcode == 0b010'1111;
+  amoxorw  : AType  : func5 == 0b0'0100, func3 == 0b010, opcode == 0b010'1111;
+  amoandw  : AType  : func5 == 0b0'1100, func3 == 0b010, opcode == 0b010'1111;
+  amoorw   : AType  : func5 == 0b0'1000, func3 == 0b010, opcode == 0b010'1111;
+  amominw  : AType  : func5 == 0b1'0000, func3 == 0b010, opcode == 0b010'1111;
+  amomaxw  : AType  : func5 == 0b1'0100, func3 == 0b010, opcode == 0b010'1111;
+  amominuw : AType  : func5 == 0b1'1000, func3 == 0b010, opcode == 0b010'1111;
+  amomaxuw : AType  : func5 == 0b1'1100, func3 == 0b010, opcode == 0b010'1111;
+  // RiscV32 single precision floating point instructions.
+  flw      : IType  : func3 == 0b010, opcode == 0b000'0111;
+  fsw      : SType  : func3 == 0b010, opcode == 0b010'0111;
+  fmadd_s  : R4Type : func2 == 0b00,  opcode == 0b100'0011;
+  fmsub_s  : R4Type : func2 == 0b00,  opcode == 0b100'0111;
+  fnmsub_s : R4Type : func2 == 0b00,  opcode == 0b100'1011;
+  fnmadd_s : R4Type : func2 == 0b00,  opcode == 0b100'1111;
+  fadd_s   : RType  : func7 == 0b000'0000, opcode == 0b101'0011;
+  fsub_s   : RType  : func7 == 0b000'0100, opcode == 0b101'0011;
+  fmul_s   : RType  : func7 == 0b000'1000, opcode == 0b101'0011;
+  fdiv_s   : RType  : func7 == 0b000'1100, opcode == 0b101'0011;
+  fsqrt_s  : RType  : func7 == 0b010'1100, rs2 == 0, opcode == 0b101'0011;
+  fsgnj_s  : RType  : func7 == 0b001'0000, func3 == 0b000, opcode == 0b101'0011;
+  fsgnjn_s : RType  : func7 == 0b001'0000, func3 == 0b001, opcode == 0b101'0011;
+  fsgnjx_s : RType  : func7 == 0b001'0000, func3 == 0b010, opcode == 0b101'0011;
+  fmin_s   : RType  : func7 == 0b001'0100, func3 == 0b000, opcode == 0b101'0011;
+  fmax_s   : RType  : func7 == 0b001'0100, func3 == 0b001, opcode == 0b101'0011;
+  fcvt_ws  : RType  : func7 == 0b110'0000, rs2 == 0, opcode == 0b101'0011;
+  fcvt_wus : RType  : func7 == 0b110'0000, rs2 == 1, opcode == 0b101'0011;
+  fmv_xw   : RType  : func7 == 0b111'0000, rs2 == 0, func3 == 0b000, opcode == 0b101'0011;
+  fcmpeq_s : RType  : func7 == 0b101'0000, func3 == 0b010, opcode == 0b101'0011;
+  fcmplt_s : RType  : func7 == 0b101'0000, func3 == 0b001, opcode == 0b101'0011;
+  fcmple_s : RType  : func7 == 0b101'0000, func3 == 0b000, opcode == 0b101'0011;
+  fclass_s : RType  : func7 == 0b111'0000, rs2 == 0, func3 == 0b001, opcode == 0b101'0011;
+  fcvt_sw  : RType  : func7 == 0b110'1000, rs2 == 0, opcode == 0b101'0011;
+  fcvt_swu : RType  : func7 == 0b110'1000, rs2 == 1, opcode == 0b101'0011;
+  fmv_wx   : RType  : func7 == 0b111'1000, rs2 == 0, func3 == 0b000, opcode == 0b101'0011;
+  // RiscV32 double precision floating point instructions.
+  fld      : IType  : func3 == 0b011, opcode == 0b000'0111;
+  fsd      : SType  : func3 == 0b011, opcode == 0b010'0111;
+  fmadd_d  : R4Type : func2 == 0b01,  opcode == 0b100'0011;
+  fmsub_d  : R4Type : func2 == 0b01,  opcode == 0b100'0111;
+  fnmsub_d : R4Type : func2 == 0b01,  opcode == 0b100'1011;
+  fnmadd_d : R4Type : func2 == 0b01,  opcode == 0b100'1111;
+  fadd_d   : RType  : func7 == 0b000'0001, opcode == 0b101'0011;
+  fsub_d   : RType  : func7 == 0b000'0101, opcode == 0b101'0011;
+  fmul_d   : RType  : func7 == 0b000'1001, opcode == 0b101'0011;
+  fdiv_d   : RType  : func7 == 0b000'1101, opcode == 0b101'0011;
+  fsqrt_d  : RType  : func7 == 0b010'1101, rs2 == 0, opcode == 0b101'0011;
+  fsgnj_d  : RType  : func7 == 0b001'0001, func3 == 0b000, opcode == 0b101'0011;
+  fsgnjn_d : RType  : func7 == 0b001'0001, func3 == 0b001, opcode == 0b101'0011;
+  fsgnjx_d : RType  : func7 == 0b001'0001, func3 == 0b010, opcode == 0b101'0011;
+  fmin_d   : RType  : func7 == 0b001'0101, func3 == 0b000, opcode == 0b101'0011;
+  fmax_d   : RType  : func7 == 0b001'0101, func3 == 0b001, opcode == 0b101'0011;
+  fcvt_sd  : RType  : func7 == 0b010'0000, rs2 == 1, opcode == 0b101'0011;
+  fcvt_ds  : RType   : func7 == 0b010'0001, rs2 == 0, opcode == 0b101'0011;
+  fcmpeq_d : RType  : func7 == 0b101'0001, func3 == 0b010, opcode == 0b101'0011;
+  fcmplt_d : RType  : func7 == 0b101'0001, func3 == 0b001, opcode == 0b101'0011;
+  fcmple_d : RType  : func7 == 0b101'0001, func3 == 0b000, opcode == 0b101'0011;
+  fclass_d : RType  : func7 == 0b111'0001, rs2 == 0, func3 == 0b001, opcode == 0b101'0011;
+  fcvt_wd  : RType  : func7 == 0b110'0001, rs2 == 0, opcode == 0b101'0011;
+  fcvt_wud : RType  : func7 == 0b110'0001, rs2 == 1, opcode == 0b101'0011;
+  fcvt_dw  : RType  : func7 == 0b110'1001, rs2 == 0, opcode == 0b101'0011;
+  fcvt_dwu : RType  : func7 == 0b110'1001, rs2 == 1, opcode == 0b101'0011;
+  // RiscV32 CSR manipulation instructions.
+  csrrw    : IType  : func3 == 0b001, rd != 0,  opcode == 0b111'0011;
+  csrrs    : IType  : func3 == 0b010, rs1 != 0, rd != 0, opcode == 0b111'0011;
+  csrrc    : IType  : func3 == 0b011, rs1 != 0, rd != 0, opcode == 0b111'0011;
+  csrrs_nr : IType  : func3 == 0b010, rs1 != 0, rd == 0, opcode == 0b111'0011;
+  csrrc_nr : IType  : func3 == 0b011, rs1 != 0, rd == 0, opcode == 0b111'0011;
+  csrrw_nr : IType  : func3 == 0b001, rd == 0,  opcode == 0b111'0011;
+  csrrs_nw : IType  : func3 == 0b010, rs1 == 0, opcode == 0b111'0011;
+  csrrc_nw : IType  : func3 == 0b011, rs1 == 0, opcode == 0b111'0011;
+  csrrwi   : IType  : func3 == 0b101, rd != 0,  opcode == 0b111'0011;
+  csrrsi   : IType  : func3 == 0b110, rs1 != 0, rd != 0, opcode == 0b111'0011;
+  csrrci   : IType  : func3 == 0b111, rs1 != 0, rd != 0, opcode == 0b111'0011;
+  csrrsi_nr: IType  : func3 == 0b110, rs1 != 0, rd == 0, opcode == 0b111'0011;
+  csrrci_nr: IType  : func3 == 0b111, rs1 != 0, rd == 0, opcode == 0b111'0011;
+  csrrwi_nr: IType  : func3 == 0b101, rd == 0,  opcode == 0b111'0011;
+  csrrsi_nw: IType  : func3 == 0b110, rs1 == 0, opcode == 0b111'0011;
+  csrrci_nw: IType  : func3 == 0b111, rs1 == 0, opcode == 0b111'0011;
+  // RiscV32 Privileged instructions.
+  uret    : Inst32Format  : bits == 0b000'0000'00010'00000'000'00000, opcode == 0b111'0011;
+  sret    : Inst32Format  : bits == 0b000'1000'00010'00000'000'00000, opcode == 0b111'0011;
+  mret    : Inst32Format  : bits == 0b001'1000'00010'00000'000'00000, opcode == 0b111'0011;
+  wfi     : Inst32Format  : bits == 0b000'1000'00101'00000'000'00000, opcode == 0b111'0011;
+  sfence_vma_zz : RType : func7 == 0b000'1001, rs2 == 0, rs1 == 0, func3 == 0, rd == 0, opcode == 0b111'0011;
+  sfence_vma_zn : RType : func7 == 0b000'1001, rs2 != 0, rs1 == 0, func3 == 0, rd == 0, opcode == 0b111'0011;
+  sfence_vma_nz : RType : func7 == 0b000'1001, rs2 == 0, rs1 != 0, func3 == 0, rd == 0, opcode == 0b111'0011;
+  sfence_vma_nn : RType : func7 == 0b000'1001, rs2 != 0, rs1 != 0, func3 == 0, rd == 0, opcode == 0b111'0011;
+};
diff --git a/mpact/sim/generic/complex_resource.cc b/mpact/sim/generic/complex_resource.cc
index 971eb04..fcf54e9 100644
--- a/mpact/sim/generic/complex_resource.cc
+++ b/mpact/sim/generic/complex_resource.cc
@@ -37,7 +37,7 @@
   size_t mod = cycle_depth_ & kLowBitMask;
   if (mod > 0) {
     // Clear bits outside the cycle_depth from the last mask.
-    mask_array_[array_size_ - 1] >>= kNumBitsPerWord - mod;
+    mask_array_[array_size_ - 1] <<= kNumBitsPerWord - mod;
   }
 }
 
@@ -58,21 +58,21 @@
     return;
   }
   int num_longwords = cycles >> 6;
-  // If shift amount is greater than 64.
+  // If shift amount is greater or equal to 64.
   if (num_longwords > 0) {
-    for (unsigned i = 0; i < array_size_; i++) {
-      unsigned index = i + num_longwords;
-      bit_array_[i] = (index < array_size_) ? bit_array_[index] : 0;
+    for (int i = array_size_ - 1; i >= 0; i--) {
+      int index = i - num_longwords;
+
+      bit_array_[i] = (index < 0) ? 0 : bit_array_[index];
     }
   }
   cycles &= kLowBitMask;
   // If cycles is now zero, return (i.e., if cycles was a multiple of 64).
   if (cycles == 0) return;
 
-  // The number of words we have to shift within the array. Anything beyond
-  // bit_array_[max - 1] was zeroed in the previous step.
-  unsigned max = array_size_ - num_longwords;
-  for (unsigned i = 0; i < max; i++) {
+  // The number of remaining words we have to shift within the array.
+  // Anything before bit_array_[num_longwords] were zeroed in the previous step.
+  for (unsigned i = num_longwords; i < array_size_; i++) {
     // For each array word, shift right by the number of remaining cycles to
     // advance. This gets rid of resource reservations from 0..cycles - 1. Then
     // or in the low "cycles" bits from the next word into the high "cycles"
diff --git a/mpact/sim/generic/counters.h b/mpact/sim/generic/counters.h
index 2f9c2bd..bf5b7ff 100644
--- a/mpact/sim/generic/counters.h
+++ b/mpact/sim/generic/counters.h
@@ -86,6 +86,12 @@
   // to Initialize(..) is required before it can be added to a component.
   CounterValueOutputBase() : is_enabled_(false), is_initialized_(false) {}
   // Constructs and initializes the counter.
+  CounterValueOutputBase(std::string name, std::string about, const T initial)
+      : name_(std::move(name)),
+        about_(about),
+        is_enabled_(true),
+        is_initialized_(true),
+        value_(std::move(initial)) {}
   CounterValueOutputBase(std::string name, const T initial)
       : name_(std::move(name)),
         about_(),
@@ -198,8 +204,12 @@
 
   // Constructor and destructor.
   SimpleCounter() : CounterValueOutputBase<T>() {}
+  SimpleCounter(std::string name, std::string about, const T &initial)
+      : CounterValueOutputBase<T>(std::move(name), std::move(about), initial) {}
   SimpleCounter(std::string name, const T &initial)
       : CounterValueOutputBase<T>(std::move(name), initial) {}
+  SimpleCounter(std::string name, std::string about)
+      : SimpleCounter(std::move(name), std::move(about), T()) {}
   explicit SimpleCounter(std::string name)
       : SimpleCounter(std::move(name), T()) {}
   SimpleCounter &operator=(const SimpleCounter &) = delete;
diff --git a/mpact/sim/generic/fifo.h b/mpact/sim/generic/fifo.h
index d821d6c..0d22168 100644
--- a/mpact/sim/generic/fifo.h
+++ b/mpact/sim/generic/fifo.h
@@ -20,6 +20,7 @@
 #include <deque>
 #include <memory>
 #include <string>
+#include <type_traits>
 #include <utility>
 #include <vector>
 
@@ -258,13 +259,28 @@
     typename std::enable_if<!std::is_integral<T>::value, void>::type;
 
 // Helper function used in the partial specialization below.
-template <typename F, typename T>
+template <
+    typename F, typename T,
+    typename std::enable_if<std::is_signed<T>::value, T>::type * = nullptr>
 inline T HelperAs(const FifoBase *fifo, int i) {
   DataBuffer *db = fifo->Front();
   if (nullptr == db) {
     return static_cast<T>(0);
   }
-  return static_cast<T>(db->Get<F>(i));
+
+  return static_cast<T>(db->Get<typename std::make_signed<F>::type>(i));
+}
+
+template <
+    typename F, typename T,
+    typename std::enable_if<std::is_unsigned<T>::value, T>::type * = nullptr>
+inline T HelperAs(const FifoBase *fifo, int i) {
+  DataBuffer *db = fifo->Front();
+  if (nullptr == db) {
+    return static_cast<T>(0);
+  }
+
+  return static_cast<T>(db->Get<typename std::make_unsigned<F>::type>(i));
 }
 
 template <typename T, typename Enable>
diff --git a/mpact/sim/generic/test/complex_resource_operand_test.cc b/mpact/sim/generic/test/complex_resource_operand_test.cc
index adcecdb..3532c6f 100644
--- a/mpact/sim/generic/test/complex_resource_operand_test.cc
+++ b/mpact/sim/generic/test/complex_resource_operand_test.cc
@@ -34,16 +34,18 @@
 // Bits 100-107 are cleared in this bit vector.
 constexpr uint64_t kFree100To107[] = {
     0xffff'ffff'ffff'ffff, 0xffff'f00f'ffff'ffff, 0xffff'ffff'ffff'ffff,
-    0x0000'03ff'ffff'ffff};
+    0xffff'ffff'ffc0'0000};
 
 // Longer than what is supported.
 constexpr uint64_t kTooLong[5] = {0xffff, 0, 0, 0, 0};
+// Longer than 234 bits.
+constexpr uint64_t kOnesTooFar[4] = {0, 0, 0, 0xffff'ffff'fff0'0000};
 // All zeros, no cycle is reserved.
 constexpr uint64_t kAllZeros[4] = {0};
 // The request vector corresponding to kFree100To107
-constexpr uint64_t kAcquire100To107[] = {~kFree100To107[0], ~kFree100To107[1],
-                                         ~kFree100To107[2],
-                                         ~kFree100To107[3] & 0x3ff'ffff'ffff};
+constexpr uint64_t kAcquire100To107[] = {
+    ~kFree100To107[0], ~kFree100To107[1], ~kFree100To107[2],
+    ~kFree100To107[3] & 0xffff'ffff'ffc0'0000};
 
 // ArchState derived class that is passed in to the resource (so that it can
 // access the clock.
@@ -76,6 +78,11 @@
   ComplexResourceOperand *operand_;
 };
 
+// Create and check name.
+TEST_F(ComplexResourceOperandTest, Create) {
+  EXPECT_EQ(operand_->AsString(), resource_->name());
+}
+
 // Check error status from setting the cycle mask.
 TEST_F(ComplexResourceOperandTest, CycleMask) {
   auto *op = new ComplexResourceOperand(nullptr);
@@ -89,7 +96,9 @@
       testing::ElementsAreArray(absl::MakeSpan(kAcquire100To107)
                                     .first(operand_->bit_array().size())));
   // Now try setting using arrays.
+  EXPECT_TRUE(absl::IsInternal(op->SetCycleMask(kAcquire100To107)));
   EXPECT_TRUE(absl::IsInvalidArgument(operand_->SetCycleMask(kTooLong)));
+  EXPECT_TRUE(absl::IsInvalidArgument(operand_->SetCycleMask(kOnesTooFar)));
   EXPECT_TRUE(absl::IsInvalidArgument(operand_->SetCycleMask(kAllZeros)));
   EXPECT_TRUE(operand_->SetCycleMask(kAcquire100To107).ok());
   delete op;
diff --git a/mpact/sim/generic/test/complex_resource_test.cc b/mpact/sim/generic/test/complex_resource_test.cc
index 24fb61d..ef400ef 100644
--- a/mpact/sim/generic/test/complex_resource_test.cc
+++ b/mpact/sim/generic/test/complex_resource_test.cc
@@ -32,10 +32,12 @@
 constexpr int kWindow256 = 256;
 constexpr size_t kCycleDepth = 234;
 constexpr char kResourceName[] = "my_resource";
-uint64_t kAllOnes[] = {0xffff'ffff'ffff'ffff, 0xffff'ffff'ffff'ffff,
-                       0xffff'ffff'ffff'ffff, 0x0000'03ff'ffff'ffff};
+uint64_t kAllOnes234[] = {0xffff'ffff'ffff'ffff, 0xffff'ffff'ffff'ffff,
+                          0xffff'ffff'ffff'ffff, 0xffff'ffff'ffc0'0000};
 uint64_t kAllOnes256[] = {0xffff'ffff'ffff'ffff, 0xffff'ffff'ffff'ffff,
                           0xffff'ffff'ffff'ffff, 0xffff'ffff'ffff'ffff};
+uint64_t kAllOnes64[] = {0xffff'ffff'ffff'ffff, 0, 0, 0};
+uint64_t kAllOnes96[] = {0xffff'ffff'ffff'ffff, 0, 0, 0};
 
 class MockArchState : public ArchState {
  public:
@@ -58,19 +60,20 @@
   auto *resource = new ComplexResource(arch_state_, kResourceName, kCycleDepth);
   EXPECT_EQ(resource->bit_array().size(), (kCycleDepth + 63) / 64);
   EXPECT_EQ(resource->name(), kResourceName);
+  EXPECT_EQ(resource->AsString(), kResourceName);
   delete resource;
 }
 
 // Verify that all bits are free to start out with.
 TEST_F(ComplexResourceTest, IsFreeMarchingOne) {
   auto *resource = new ComplexResource(arch_state_, kResourceName, kCycleDepth);
-  uint64_t marching_one[4] = {1, 0, 0, 0};
+  uint64_t marching_one[4] = {0x8000'0000'0000'0000, 0, 0, 0};
   for (size_t i = 0; i < kCycleDepth; i++) {
     EXPECT_TRUE(resource->IsFree(marching_one)) << i;
     uint64_t prev_bit = 0;
     for (int j = 0; j < 4; j++) {
       uint64_t bit = (marching_one[j] >> 63) & 0x1;
-      marching_one[j] <<= 1;
+      marching_one[j] >>= 1;
       marching_one[j] |= prev_bit;
       prev_bit = bit;
     }
@@ -81,15 +84,15 @@
 // Reserve all bits, verify that they are set.
 TEST_F(ComplexResourceTest, IsBusyMarchingOne) {
   auto *resource = new ComplexResource(arch_state_, kResourceName, kCycleDepth);
-  resource->Acquire(kAllOnes);
-  uint64_t marching_one[4] = {1, 0, 0, 0};
+  resource->Acquire(kAllOnes234);
+  uint64_t marching_one[4] = {0x8000'0000'0000'0000, 0, 0, 0};
   for (size_t i = 0; i < kCycleDepth; i++) {
     EXPECT_FALSE(resource->IsFree(marching_one)) << i;
     uint64_t prev_bit = 0;
     for (int j = 0; j < 4; j++) {
-      uint64_t bit = (marching_one[j] >> 63) & 0x1;
-      marching_one[j] <<= 1;
-      marching_one[j] |= prev_bit;
+      uint64_t bit = (marching_one[j]) & 0x1;
+      marching_one[j] >>= 1;
+      marching_one[j] |= (prev_bit << 63);
       prev_bit = bit;
     }
   }
@@ -100,8 +103,8 @@
 // try to acquire it. Release the resource for that cycle, then try again.
 TEST_F(ComplexResourceTest, AcquireRelease) {
   auto *resource = new ComplexResource(arch_state_, kResourceName, kCycleDepth);
-  resource->Acquire(kAllOnes);
-  uint64_t marching_one[4] = {1, 0, 0, 0};
+  resource->Acquire(kAllOnes234);
+  uint64_t marching_one[4] = {0x8000'0000'0000'0000, 0, 0, 0};
   for (size_t i = 0; i < kCycleDepth; i++) {
     EXPECT_FALSE(resource->IsFree(marching_one)) << i;
     resource->Release(marching_one);
@@ -109,9 +112,9 @@
     resource->Acquire(marching_one);
     uint64_t prev_bit = 0;
     for (int j = 0; j < 4; j++) {
-      uint64_t bit = (marching_one[j] >> 63) & 0x1;
-      marching_one[j] <<= 1;
-      marching_one[j] |= prev_bit;
+      uint64_t bit = (marching_one[j]) & 0x1;
+      marching_one[j] >>= 1;
+      marching_one[j] |= (prev_bit << 63);
       prev_bit = bit;
     }
   }
@@ -123,9 +126,9 @@
 // the resource is free in the last cycle, next to last cycle, etc.
 TEST_F(ComplexResourceTest, SingleWordBy1) {
   auto *resource = new ComplexResource(arch_state_, kResourceName, 64);
-  EXPECT_TRUE(resource->IsFree(kAllOnes));
-  resource->Acquire(kAllOnes);
-  EXPECT_FALSE(resource->IsFree(kAllOnes));
+  EXPECT_TRUE(resource->IsFree(kAllOnes234));
+  resource->Acquire(kAllOnes234);
+  EXPECT_FALSE(resource->IsFree(kAllOnes234));
   uint64_t mask_array[1] = {0};
   uint64_t mask = 0xffff'ffff'ffff'ffff;
   // Increment cycle by 1.
@@ -145,16 +148,16 @@
         << std::endl;
   }
   arch_state_->set_cycle(cycle);
-  EXPECT_TRUE(resource->IsFree(kAllOnes)) << cycle;
+  EXPECT_TRUE(resource->IsFree(kAllOnes234)) << cycle;
   delete resource;
 }
 
 // Same as above, but advanced the clock by 3.
 TEST_F(ComplexResourceTest, SingleWordBy3) {
   auto *resource = new ComplexResource(arch_state_, kResourceName, 64);
-  EXPECT_TRUE(resource->IsFree(kAllOnes));
-  resource->Acquire(kAllOnes);
-  EXPECT_FALSE(resource->IsFree(kAllOnes));
+  EXPECT_TRUE(resource->IsFree(kAllOnes234));
+  resource->Acquire(kAllOnes234);
+  EXPECT_FALSE(resource->IsFree(kAllOnes234));
   uint64_t mask_array[1] = {0};
   uint64_t mask = 0xffff'ffff'ffff'ffff;
   // Increment cycle by 3.
@@ -174,7 +177,7 @@
         << std::endl;
   }
   arch_state_->set_cycle(cycle);
-  EXPECT_TRUE(resource->IsFree(kAllOnes)) << cycle;
+  EXPECT_TRUE(resource->IsFree(kAllOnes234)) << cycle;
   delete resource;
 }
 
@@ -210,7 +213,7 @@
         << std::endl;
   }
   arch_state_->set_cycle(cycle);
-  EXPECT_TRUE(resource->IsFree(kAllOnes));
+  EXPECT_TRUE(resource->IsFree(kAllOnes234));
   delete resource;
 }
 
@@ -244,7 +247,29 @@
         << std::endl;
   }
   arch_state_->set_cycle(cycle);
-  EXPECT_TRUE(resource->IsFree(kAllOnes));
+  EXPECT_TRUE(resource->IsFree(kAllOnes256));
+  delete resource;
+}
+
+// Check that advancing clock by more than 256 yields free resource.
+TEST_F(ComplexResourceTest, ShiftGreaterThan256) {
+  auto *resource = new ComplexResource(arch_state_, kResourceName, 256);
+  EXPECT_TRUE(resource->IsFree(kAllOnes256));
+  resource->Acquire(kAllOnes256);
+  EXPECT_FALSE(resource->IsFree(kAllOnes256));
+  arch_state_->set_cycle(300);
+  EXPECT_TRUE(resource->IsFree(kAllOnes256));
+  delete resource;
+}
+
+// Verify that shifts over 64 bits work.
+TEST_F(ComplexResourceTest, ShiftGreaterThan64) {
+  auto *resource = new ComplexResource(arch_state_, kResourceName, 256);
+  EXPECT_TRUE(resource->IsFree(kAllOnes256));
+  resource->Acquire(kAllOnes256);
+  EXPECT_FALSE(resource->IsFree(kAllOnes64));
+  arch_state_->set_cycle(96);
+  EXPECT_TRUE(resource->IsFree(kAllOnes96));
   delete resource;
 }
 
diff --git a/mpact/sim/generic/test/component_test.cc b/mpact/sim/generic/test/component_test.cc
index 84a2682..7ed65fc 100644
--- a/mpact/sim/generic/test/component_test.cc
+++ b/mpact/sim/generic/test/component_test.cc
@@ -61,6 +61,48 @@
   }
 )pb";
 
+constexpr char kImportProtoMalformed[] = R"pb(
+  name: "top"
+  configuration { sint64_value: -654 }
+  statistics { name: "int64_counter" sint64_value: -321 }
+  component_data {
+    name: "child"
+    configuration { name: "uint64_config" uint64_value: 321 }
+    statistics { name: "uint64_counter" uint64_value: 654 }
+  }
+)pb";
+
+constexpr char kImportProtoChildNameMissing[] = R"pb(
+  name: "top"
+  configuration { name: "int64_config" sint64_value: -654 }
+  statistics { name: "int64_counter" sint64_value: -321 }
+  component_data {
+    configuration { name: "uint64_config" uint64_value: 321 }
+    statistics { name: "uint64_counter" uint64_value: 654 }
+  }
+)pb";
+
+constexpr char kImportProtoNameMissing[] = R"pb(
+  configuration { name: "int64_config" sint64_value: -654 }
+  statistics { name: "int64_counter" sint64_value: -321 }
+  component_data {
+    name: "child"
+    configuration { name: "uint64_config" uint64_value: 321 }
+    statistics { name: "uint64_counter" uint64_value: 654 }
+  }
+)pb";
+
+constexpr char kImportProtoNameMismatch[] = R"pb(
+  name: "not_top"
+  configuration { name: "int64_config" sint64_value: -654 }
+  statistics { name: "int64_counter" sint64_value: -321 }
+  component_data {
+    name: "child"
+    configuration { name: "uint64_config" uint64_value: 321 }
+    statistics { name: "uint64_counter" uint64_value: 654 }
+  }
+)pb";
+
 // Test fixture. Allocates two components, two counters and two config items.
 // The counters and config items are not added to the components in the fixture,
 // but rather in each test as needed.
@@ -78,6 +120,9 @@
   Component &child() { return child_; }
   SimpleCounter<int64_t> &int64_counter() { return int64_counter_; }
   SimpleCounter<uint64_t> &uint64_counter() { return uint64_counter_; }
+  SimpleCounter<uint64_t> &uninitialized_counter() {
+    return uninitialized_counter_;
+  }
   Config<int64_t> &int64_config() { return int64_config_; }
   Config<uint64_t> &uint64_config() { return uint64_config_; }
 
@@ -86,6 +131,7 @@
   Component child_;
   SimpleCounter<int64_t> int64_counter_;
   SimpleCounter<uint64_t> uint64_counter_;
+  SimpleCounter<uint64_t> uninitialized_counter_;
   Config<int64_t> int64_config_;
   Config<uint64_t> uint64_config_;
 };
@@ -113,9 +159,11 @@
 TEST_F(ComponentTest, ComponentsWithCounters) {
   auto *top_counter = &int64_counter();
   auto *child_counter = &uint64_counter();
+  auto *uninit_counter = &uninitialized_counter();
 
   EXPECT_TRUE(top().AddCounter(top_counter).ok());
   EXPECT_TRUE(child().AddCounter(child_counter).ok());
+  EXPECT_TRUE(absl::IsInvalidArgument(top().AddCounter(uninit_counter)));
 
   EXPECT_EQ(top().GetCounter(kInt64CounterName), top_counter);
   EXPECT_EQ(top().GetCounter(kUint64CounterName), nullptr);
@@ -165,6 +213,84 @@
 
   auto exported_proto = std::make_unique<ComponentData>();
   EXPECT_TRUE(top().Export(exported_proto.get()).ok());
+
+  EXPECT_TRUE(absl::IsInvalidArgument(top().Export(nullptr)));
+}
+
+// Failed import due to missing top-level name.
+TEST_F(ComponentTest, ImportTestNameMissing) {
+  auto *top_config = &int64_config();
+  auto *child_config = &uint64_config();
+  EXPECT_TRUE(top().AddConfig(top_config).ok());
+  EXPECT_TRUE(child().AddConfig(child_config).ok());
+
+  auto *top_counter = &int64_counter();
+  auto *child_counter = &uint64_counter();
+  EXPECT_TRUE(top().AddCounter(top_counter).ok());
+  EXPECT_TRUE(child().AddCounter(child_counter).ok());
+
+  ComponentData from_text;
+  EXPECT_TRUE(google::protobuf::TextFormat::ParseFromString(
+      kImportProtoNameMissing, &from_text));
+  // Perform the import, expect failure.
+  EXPECT_TRUE(absl::IsInternal(top().Import(from_text)));
+}
+
+// Failed import due to malformed component data.
+TEST_F(ComponentTest, ImportTestMalformed) {
+  auto *top_config = &int64_config();
+  auto *child_config = &uint64_config();
+  EXPECT_TRUE(top().AddConfig(top_config).ok());
+  EXPECT_TRUE(child().AddConfig(child_config).ok());
+
+  auto *top_counter = &int64_counter();
+  auto *child_counter = &uint64_counter();
+  EXPECT_TRUE(top().AddCounter(top_counter).ok());
+  EXPECT_TRUE(child().AddCounter(child_counter).ok());
+
+  ComponentData from_text;
+  EXPECT_TRUE(google::protobuf::TextFormat::ParseFromString(
+      kImportProtoMalformed, &from_text));
+  // Perform the import, expect failure.
+  EXPECT_TRUE(absl::IsInternal(top().Import(from_text)));
+}
+
+// Failed import due to missing child name.
+TEST_F(ComponentTest, ImportTestChildNameMissing) {
+  auto *top_config = &int64_config();
+  auto *child_config = &uint64_config();
+  EXPECT_TRUE(top().AddConfig(top_config).ok());
+  EXPECT_TRUE(child().AddConfig(child_config).ok());
+
+  auto *top_counter = &int64_counter();
+  auto *child_counter = &uint64_counter();
+  EXPECT_TRUE(top().AddCounter(top_counter).ok());
+  EXPECT_TRUE(child().AddCounter(child_counter).ok());
+
+  ComponentData from_text;
+  EXPECT_TRUE(google::protobuf::TextFormat::ParseFromString(
+      kImportProtoChildNameMissing, &from_text));
+  // Perform the import, expect failure.
+  EXPECT_TRUE(absl::IsInternal(top().Import(from_text)));
+}
+
+// Failed import due to top level name mismatch.
+TEST_F(ComponentTest, ImportTestNameMismatch) {
+  auto *top_config = &int64_config();
+  auto *child_config = &uint64_config();
+  EXPECT_TRUE(top().AddConfig(top_config).ok());
+  EXPECT_TRUE(child().AddConfig(child_config).ok());
+
+  auto *top_counter = &int64_counter();
+  auto *child_counter = &uint64_counter();
+  EXPECT_TRUE(top().AddCounter(top_counter).ok());
+  EXPECT_TRUE(child().AddCounter(child_counter).ok());
+
+  ComponentData from_text;
+  EXPECT_TRUE(google::protobuf::TextFormat::ParseFromString(
+      kImportProtoNameMismatch, &from_text));
+  // Perform the import, expect failure.
+  EXPECT_TRUE(absl::IsInternal(top().Import(from_text)));
 }
 
 // Import a proto into the components. Verify that the value of config entries
diff --git a/mpact/sim/generic/test/config_test.cc b/mpact/sim/generic/test/config_test.cc
index 5ee3b23..e2ad820 100644
--- a/mpact/sim/generic/test/config_test.cc
+++ b/mpact/sim/generic/test/config_test.cc
@@ -77,14 +77,19 @@
 TEST(ConfigTest, BaseConstruction) {
   Config<bool> bool_config(kBoolConfigName);
   EXPECT_EQ(bool_config.name(), kBoolConfigName);
+  EXPECT_FALSE(bool_config.HasConfigValue());
   Config<int64_t> int64_config(kInt64ConfigName);
   EXPECT_EQ(int64_config.name(), kInt64ConfigName);
+  EXPECT_FALSE(int64_config.HasConfigValue());
   Config<uint64_t> uint64_config(kUint64ConfigName);
   EXPECT_EQ(uint64_config.name(), kUint64ConfigName);
+  EXPECT_FALSE(uint64_config.HasConfigValue());
   Config<double> double_config(kDoubleConfigName);
   EXPECT_EQ(double_config.name(), kDoubleConfigName);
+  EXPECT_FALSE(double_config.HasConfigValue());
   Config<std::string> string_config(kStringConfigName);
   EXPECT_EQ(string_config.name(), kStringConfigName);
+  EXPECT_FALSE(string_config.HasConfigValue());
 }
 
 // Testing that the value can be set and retrieved using the variant type.
@@ -95,30 +100,35 @@
   Config<bool> bool_config(kBoolConfigName);
   input_value = ConfigValue(kBoolValue);
   EXPECT_TRUE(bool_config.SetConfigValue(input_value).ok());
+  EXPECT_TRUE(bool_config.HasConfigValue());
   output_value = bool_config.GetConfigValue();
   EXPECT_EQ(std::get<bool>(output_value), kBoolValue);
 
   Config<int64_t> int64_config(kInt64ConfigName);
   input_value = ConfigValue(kInt64Value);
   EXPECT_TRUE(int64_config.SetConfigValue(input_value).ok());
+  EXPECT_TRUE(int64_config.HasConfigValue());
   output_value = int64_config.GetConfigValue();
   EXPECT_EQ(std::get<int64_t>(output_value), kInt64Value);
 
   Config<uint64_t> uint64_config(kUint64ConfigName);
   input_value = ConfigValue(kUint64Value);
   EXPECT_TRUE(uint64_config.SetConfigValue(input_value).ok());
+  EXPECT_TRUE(uint64_config.HasConfigValue());
   output_value = uint64_config.GetConfigValue();
   EXPECT_EQ(std::get<uint64_t>(output_value), kUint64Value);
 
   Config<double> double_config(kDoubleConfigName);
   input_value = ConfigValue(kDoubleValue);
   EXPECT_TRUE(double_config.SetConfigValue(input_value).ok());
+  EXPECT_TRUE(double_config.HasConfigValue());
   output_value = double_config.GetConfigValue();
   EXPECT_EQ(std::get<double>(output_value), kDoubleValue);
 
   Config<std::string> string_config(kStringConfigName);
   input_value = ConfigValue(std::string(kStringValue));
   EXPECT_TRUE(string_config.SetConfigValue(input_value).ok());
+  EXPECT_TRUE(string_config.HasConfigValue());
   output_value = string_config.GetConfigValue();
   EXPECT_EQ(std::get<std::string>(output_value), kStringValue);
 }
diff --git a/mpact/sim/generic/test/counters_test.cc b/mpact/sim/generic/test/counters_test.cc
index b949ddf..045fe8d 100644
--- a/mpact/sim/generic/test/counters_test.cc
+++ b/mpact/sim/generic/test/counters_test.cc
@@ -97,7 +97,7 @@
 TEST(CountersTest, CounterBaseInterface) {
   std::string myname("this_is_a_name");
   char myname2[] = "this_is_also_a_name";
-  SimpleCounter<int64_t> counterone(myname);
+  SimpleCounter<int64_t> counterone(myname, "about this counter");
   SimpleCounter<int64_t> countertwo(myname2);
   SimpleCounter<int64_t> int64_counter(kSimpleCounterName);
   EXPECT_EQ(int64_counter.GetName(), kSimpleCounterName);
@@ -157,6 +157,10 @@
   CounterValue cv = int64_counter.GetCounterValue();
   EXPECT_EQ(std::get<int64_t>(cv), kMinusFive);
   EXPECT_EQ(int64_counter.ToString(), absl::StrCat(kMinusFive));
+  EXPECT_EQ(static_cast<mpact::sim::generic::CounterValueOutputBase<int64_t> *>(
+                &int64_counter)
+                ->ToString(),
+            absl::StrCat(kMinusFive));
 }
 
 // Tests the SetValue call in the input interface.
@@ -223,11 +227,13 @@
   }
   // Verify that the element count is the same as the vector size.
   EXPECT_EQ(count.GetValue(), values.size());
+  EXPECT_EQ(count.ToString(), absl::StrCat(values.size()));
 }
 
 // Tests export of counter values to proto message.
 TEST(CountersTest, ExportTest) {
-  SimpleCounter<uint64_t> uint64_counter(kUint64CounterName, kUint64Value);
+  SimpleCounter<uint64_t> uint64_counter(kUint64CounterName,
+                                         "About this counter", kUint64Value);
   SimpleCounter<int64_t> int64_counter(kInt64CounterName, kInt64Value);
   SimpleCounter<double> double_counter(kDoubleCounterName, kDoubleValue);
 
diff --git a/mpact/sim/generic/test/fifo_operand_test.cc b/mpact/sim/generic/test/fifo_operand_test.cc
index 2f94ec1..839560c 100644
--- a/mpact/sim/generic/test/fifo_operand_test.cc
+++ b/mpact/sim/generic/test/fifo_operand_test.cc
@@ -35,6 +35,8 @@
 using Vector8Fifo = VectorFifo<uint32_t, 8>;
 
 constexpr int kFifoCapacity = 3;
+constexpr char kScalarFifoName[] = "S0";
+constexpr char kVectorFifoName[] = "S0";
 
 // Define a class that derives from ArchState since constructors are
 // protected.
@@ -51,8 +53,8 @@
  protected:
   FifoOperandTest() {
     arch_state_ = new MockArchState("MockArchState");
-    sfifo_ = new ScalarFifo(arch_state_, "S0", kFifoCapacity);
-    vfifo_ = new Vector8Fifo(arch_state_, "V0", kFifoCapacity);
+    sfifo_ = new ScalarFifo(arch_state_, kScalarFifoName, kFifoCapacity);
+    vfifo_ = new Vector8Fifo(arch_state_, kVectorFifoName, kFifoCapacity);
   }
 
   ~FifoOperandTest() override {
@@ -72,11 +74,19 @@
   EXPECT_EQ(std::any_cast<FifoBase *>(s_src_op->GetObject()),
             static_cast<FifoBase *>(sfifo_));
   EXPECT_EQ(s_src_op->shape(), sfifo_->shape());
+  EXPECT_EQ(s_src_op->AsString(), kScalarFifoName);
+
+  s_src_op = std::make_unique<FifoSourceOperand<uint32_t>>(sfifo_, "Fifo");
+  EXPECT_EQ(s_src_op->AsString(), "Fifo");
 
   auto v_src_op = std::make_unique<FifoSourceOperand<uint32_t>>(vfifo_);
   EXPECT_EQ(std::any_cast<FifoBase *>(v_src_op->GetObject()),
             static_cast<FifoBase *>(vfifo_));
   EXPECT_EQ(v_src_op->shape(), vfifo_->shape());
+  EXPECT_EQ(v_src_op->AsString(), kVectorFifoName);
+
+  v_src_op = std::make_unique<FifoSourceOperand<uint32_t>>(vfifo_, "Fifo");
+  EXPECT_EQ(v_src_op->AsString(), "Fifo");
 }
 
 // Tests that the fifo destination operands are initialized correctly.
@@ -84,10 +94,26 @@
   auto s_dst_op = std::make_unique<FifoDestinationOperand<uint32_t>>(sfifo_, 1);
   EXPECT_EQ(s_dst_op->latency(), 1);
   EXPECT_EQ(s_dst_op->shape(), sfifo_->shape());
+  EXPECT_EQ(s_dst_op->CopyDataBuffer(), nullptr);
+  EXPECT_EQ(std::any_cast<FifoBase *>(s_dst_op->GetObject()),
+            static_cast<FifoBase *>(sfifo_));
+  EXPECT_EQ(s_dst_op->AsString(), kScalarFifoName);
+
+  s_dst_op =
+      std::make_unique<FifoDestinationOperand<uint32_t>>(sfifo_, 1, "Fifo");
+  EXPECT_EQ(s_dst_op->AsString(), "Fifo");
 
   auto v_dst_op = std::make_unique<FifoDestinationOperand<uint32_t>>(vfifo_, 4);
   EXPECT_EQ(v_dst_op->latency(), 4);
   EXPECT_EQ(v_dst_op->shape(), vfifo_->shape());
+  EXPECT_EQ(v_dst_op->CopyDataBuffer(), nullptr);
+  EXPECT_EQ(std::any_cast<FifoBase *>(v_dst_op->GetObject()),
+            static_cast<FifoBase *>(vfifo_));
+  EXPECT_EQ(v_dst_op->AsString(), kVectorFifoName);
+
+  v_dst_op =
+      std::make_unique<FifoDestinationOperand<uint32_t>>(vfifo_, 1, "Fifo");
+  EXPECT_EQ(v_dst_op->AsString(), "Fifo");
 }
 
 // Tests that a destination fifo operand can update a fifo so that
@@ -105,11 +131,19 @@
   arch_state_->AdvanceDelayLines();
 
   // Verify that the source operand can read the new value.
+  EXPECT_EQ(src_op->AsBool(0), true);
+  EXPECT_EQ(src_op->AsInt8(0), static_cast<int8_t>(0xEF));
+  EXPECT_EQ(src_op->AsUint8(0), 0xEF);
+  EXPECT_EQ(src_op->AsInt16(0), static_cast<int16_t>(0xBEEF));
+  EXPECT_EQ(src_op->AsUint16(0), 0xBEEF);
+  EXPECT_EQ(src_op->AsInt32(0), static_cast<int32_t>(0xDEADBEEF));
   EXPECT_EQ(src_op->AsUint32(0), 0xDEADBEEF);
+  EXPECT_EQ(src_op->AsInt64(0), static_cast<int32_t>(0xDEADBEEF));
+  EXPECT_EQ(src_op->AsUint64(0), 0xDEADBEEF);
 
-  // Get a new data buffer and initialize it to zero.
+  // Get a new data buffer and initialize it to a new value.
   DataBuffer *db2 = dst_op->AllocateDataBuffer();
-  db2->Set<uint32_t>(0, 0);
+  db2->Set<uint32_t>(0, 0xA5A55A5A);
 
   // Submit the data buffer and advance the delay line 1 cycle.
   db2->Submit();
@@ -120,7 +154,12 @@
 
   // Pop the fifo and verify the new value is there.
   std::any_cast<FifoBase *>(src_op->GetObject())->Pop();
+  EXPECT_EQ(src_op->AsUint32(0), 0xA5A55A5A);
+
+  // Pop the fifo. It is now empty, so attempting to get a value will return 0.
+  std::any_cast<FifoBase *>(src_op->GetObject())->Pop();
   EXPECT_EQ(src_op->AsUint32(0), 0);
+  EXPECT_EQ(src_op->AsInt32(0), 0);
 
   delete dst_op;
   delete src_op;
diff --git a/mpact/sim/util/memory/test/flat_demand_memory_test.cc b/mpact/sim/util/memory/test/flat_demand_memory_test.cc
index 660b220..1b82e25 100644
--- a/mpact/sim/util/memory/test/flat_demand_memory_test.cc
+++ b/mpact/sim/util/memory/test/flat_demand_memory_test.cc
@@ -14,6 +14,7 @@
 
 #include "mpact/sim/util/memory/flat_demand_memory.h"
 
+#include <cstdint>
 #include <cstring>
 #include <memory>