Added new source/destination, and resource specification in the .isa files.

This specification allows you to specify an operand (resource) as [name].
This translates into an enum entry of k_Name_. This operand/resource
specification is used in cases where it may genereate multiple operands
or resourse references.

For instance, in RiscV push/pop instructions a field 'rlist' is used to
specify a value in the range [4..15] that represents different length
lists of registers. Instead of having to specify the instruction explicitly
for each possible value of 'rlist', the [rlist] specification will allow
new GetResource/GetSource/GetDestination calls in the encoding interface
to return multiple source/destination/resource operands during the decoding
of the instruction.

PiperOrigin-RevId: 692247798
Change-Id: Ie6a08d2df349046a1ccd98c4acf8025a968451d0
diff --git a/mpact/sim/decoder/InstructionSet.g4 b/mpact/sim/decoder/InstructionSet.g4
index 431863c..f26e41e 100644
--- a/mpact/sim/decoder/InstructionSet.g4
+++ b/mpact/sim/decoder/InstructionSet.g4
@@ -312,7 +312,16 @@
   ;
 
 opcode_operands
-  : pred=IDENT? (':' source=ident_list? ( ':' dest_list? )? )?
+  : pred=IDENT? (':' source=source_list? ( ':' dest_list? )? )?
+  ;
+
+source_list
+  : source_operand (',' source_operand)*
+  ;
+
+source_operand
+  : source=IDENT
+  | '[' array_source=IDENT ']'
   ;
 
 // Destination operands may include a latency.
@@ -322,7 +331,8 @@
   ;
 
 dest_operand
-  : dest=IDENT ( '(' (expression | wildcard='*' ) ')' )?
+  : (dest=IDENT | '[' array_dest=IDENT ']')
+    ( '(' (expression | wildcard='*' ) ')' )?
   ;
 
 // Special rules for generator instruction descriptions.
@@ -441,7 +451,7 @@
 // x[2]: x is acquired starting at cycle 2 through the instruction latency.
 
 resource_item
-  : name=IDENT
+  : ( name=IDENT | '[' array_name=IDENT ']' )
         ('[' (begin_cycle=expression)? ('..' end_cycle=expression? )? ']')?
   ;
 
@@ -499,7 +509,7 @@
 OPCODES : 'opcodes';
 OVERRIDE : 'override';
 NAMESPACE : 'namespace';
-RESOURCES: 'resources';
+RESOURCES : 'resources';
 SEMFUNC : 'semfunc';
 SLOT : 'slot';
 SLOTS : 'slots';
diff --git a/mpact/sim/decoder/instruction.cc b/mpact/sim/decoder/instruction.cc
index c9a7309..6232b11 100644
--- a/mpact/sim/decoder/instruction.cc
+++ b/mpact/sim/decoder/instruction.cc
@@ -185,8 +185,8 @@
     }
     end_expr = result.value();
   }
-  auto *new_ref =
-      new ResourceReference(ref->resource, ref->dest_op, begin_expr, end_expr);
+  auto *new_ref = new ResourceReference(ref->resource, ref->is_array,
+                                        ref->dest_op, begin_expr, end_expr);
   return new_ref;
 }
 
diff --git a/mpact/sim/decoder/instruction_set.cc b/mpact/sim/decoder/instruction_set.cc
index f3bb740..f2fe00c 100644
--- a/mpact/sim/decoder/instruction_set.cc
+++ b/mpact/sim/decoder/instruction_set.cc
@@ -169,12 +169,24 @@
 //                                             OpcodeEnum opcode,
 //                                             SourceOpEnum source,
 //                                             int source_no) = 0;
+//   virtual std::vector<SourceOperandInterface *> GetSources(
+//               SlotEnum slot,
+//               int entry,
+//               OpcodeEnum opcode,
+//               ListSourceOpEnum list_source_op,
+//               int source_no) = 0;
 //   virtual DestinationOperandInterface *GetDestination(int latency,
 //                                                       SlotEnum slot,
 //                                                       int entry,
 //                                                       OpcodeEnum opcode,
 //                                                       DestOpEnum dest,
 //                                                       int dest_no) = 0;
+//   virtual std::vector<DestinationOperandInterface *> GetDestinations(
+//               SlotEnum slot,
+//               int entry,
+//               OpcodeEnum opcode,
+//               ListDestOpEnum dest_op,
+//               int dest_no) = 0;
 //   virtual int GetLatency(SlotEnum slot, int entry, OpcodeEnum opcode,
 //                          DestOpEnum dest) = 0;
 //   virtual ResourceOperandInterface *GetSimpleResourceOperand(
@@ -438,7 +450,9 @@
   // Btree sets to sort by name.
   absl::btree_set<std::string> predicate_operands;
   absl::btree_set<std::string> source_operands;
+  absl::btree_set<std::string> list_source_operands;
   absl::btree_set<std::string> dest_operands;
+  absl::btree_set<std::string> list_dest_operands;
   absl::btree_set<std::string> dest_latency;
   // Insert PascalCase operand names into the sets to select unique names.
   for (auto const *slot : slot_order_) {
@@ -450,11 +464,19 @@
         if (!opcode->predicate_op_name().empty()) {
           predicate_operands.insert(ToPascalCase(opcode->predicate_op_name()));
         }
-        for (auto const &source_op_name : opcode->source_op_name_vec()) {
-          source_operands.insert(ToPascalCase(source_op_name));
+        for (auto const &source_op : opcode->source_op_vec()) {
+          if (source_op.is_array) {
+            list_source_operands.insert(ToPascalCase(source_op.name));
+          } else {
+            source_operands.insert(ToPascalCase(source_op.name));
+          }
         }
         for (auto const *dest_op : opcode->dest_op_vec()) {
-          dest_operands.insert(dest_op->pascal_case_name());
+          if (dest_op->is_array()) {
+            list_dest_operands.insert(dest_op->pascal_case_name());
+          } else {
+            dest_operands.insert(dest_op->pascal_case_name());
+          }
           if (dest_op->expression() == nullptr) {
             dest_latency.insert(dest_op->pascal_case_name());
           }
@@ -483,6 +505,17 @@
   absl::StrAppend(&h_output, "    kPastMaxValue = ", src_count,
                   ",\n"
                   "  };\n\n");
+  // Create enum for list source operands.
+  absl::StrAppend(&h_output, "  enum class ListSourceOpEnum {\n");
+  int list_src_count = 0;
+  absl::StrAppend(&h_output, "    kNone = ", list_src_count++, ",\n");
+  for (auto const &source_name : list_source_operands) {
+    absl::StrAppend(&h_output, "    k", source_name, " = ", list_src_count++,
+                    ",\n");
+  }
+  absl::StrAppend(&h_output, "    kPastMaxValue = ", list_src_count,
+                  ",\n"
+                  "  };\n\n");
   // Create enum for destination operands.
   absl::StrAppend(&h_output, "  enum class DestOpEnum {\n");
   int dst_count = 0;
@@ -493,7 +526,17 @@
   absl::StrAppend(&h_output, "    kPastMaxValue = ", dst_count,
                   ",\n"
                   "  };\n\n");
-
+  // Create enum for list destination operands.
+  absl::StrAppend(&h_output, "  enum class ListDestOpEnum {\n");
+  int list_dst_count = 0;
+  absl::StrAppend(&h_output, "    kNone = ", list_dst_count++, ",\n");
+  for (auto const &dest_name : list_dest_operands) {
+    absl::StrAppend(&h_output, "    k", dest_name, " = ", list_dst_count++,
+                    ",\n");
+  }
+  absl::StrAppend(&h_output, "    kPastMaxValue = ", list_dst_count,
+                  ",\n"
+                  "  };\n\n");
   // Emit opcode enumeration type.
   absl::StrAppend(&h_output,
                   "  enum class OpcodeEnum {\n"
@@ -540,14 +583,14 @@
   }
   absl::StrAppend(&h_output, "    kPastMaxValue = ", resource_count,
                   "\n  };\n\n");
-
+  // Complex resource enumeration type.
   absl::StrAppend(&h_output,
                   "  enum class ComplexResourceEnum {\n"
                   "    kNone = 0,\n");
   resource_count = 1;
   name_set.clear();
   for (auto const &[unused, resource_ptr] : resource_factory_->resource_map()) {
-    if (!resource_ptr->is_simple()) {
+    if (!resource_ptr->is_simple() && !resource_ptr->is_array()) {
       name_set.insert(resource_ptr->pascal_name());
     }
   }
@@ -556,7 +599,22 @@
   }
   absl::StrAppend(&h_output, "    kPastMaxValue = ", resource_count,
                   "\n  };\n\n");
-
+  // List complex resource enumeration type.
+  absl::StrAppend(&h_output,
+                  "  enum class ListComplexResourceEnum {\n"
+                  "    kNone = 0,\n");
+  resource_count = 1;
+  name_set.clear();
+  for (auto const &[unused, resource_ptr] : resource_factory_->resource_map()) {
+    if (!resource_ptr->is_simple() && resource_ptr->is_array()) {
+      name_set.insert(resource_ptr->pascal_name());
+    }
+  }
+  for (auto const &name : name_set) {
+    absl::StrAppend(&h_output, "    k", name, " = ", resource_count++, ",\n");
+  }
+  absl::StrAppend(&h_output, "    kPastMaxValue = ", resource_count,
+                  "\n  };\n\n");
   // Emit instruction attribute types.
   absl::StrAppend(&h_output, "  enum class AttributeEnum {\n");
   int attribute_count = 0;
diff --git a/mpact/sim/decoder/instruction_set_contexts.h b/mpact/sim/decoder/instruction_set_contexts.h
index 2838810..3ae2e15 100644
--- a/mpact/sim/decoder/instruction_set_contexts.h
+++ b/mpact/sim/decoder/instruction_set_contexts.h
@@ -1,3 +1,17 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
 #ifndef MPACT_SIM_DECODER_INSTRUCTION_SET_CONTEXTS_H_
 #define MPACT_SIM_DECODER_INSTRUCTION_SET_CONTEXTS_H_
 
diff --git a/mpact/sim/decoder/instruction_set_visitor.cc b/mpact/sim/decoder/instruction_set_visitor.cc
index 579f96c..0374561 100644
--- a/mpact/sim/decoder/instruction_set_visitor.cc
+++ b/mpact/sim/decoder/instruction_set_visitor.cc
@@ -21,7 +21,6 @@
 #include <iostream>
 #include <istream>
 #include <memory>
-#include <new>
 #include <optional>
 #include <string>
 #include <utility>
@@ -1285,9 +1284,20 @@
   auto *factory = slot->instruction_set()->resource_factory();
   DestinationOperand *dest_op = nullptr;
   // Extract the text from the resource reference.
-  std::string ident_text = resource_item->name->getText();
+  std::string ident_text;
+  bool is_array;
+  if (resource_item->name != nullptr) {
+    ident_text = resource_item->name->getText();
+    is_array = false;
+  } else {
+    // Prepend the name with [] so that it doesn't conflict with a non-array
+    // resource of the same name.
+    ident_text = resource_item->array_name->getText();
+    is_array = true;
+  }
   dest_op = inst->GetDestOp(ident_text);
   auto *resource = factory->GetOrInsertResource(ident_text);
+  resource->set_is_array(is_array);
   // Compute begin and end values.
   TemplateExpression *begin_expr;
   TemplateExpression *end_expr;
@@ -1346,7 +1356,8 @@
         {resource_item->end_cycle, context_file_map_.at(slot->ctx())});
     end_expr = VisitExpression(resource_item->end_cycle, slot, inst);
   }
-  auto *ref = new ResourceReference(resource, dest_op, begin_expr, end_expr);
+  auto *ref =
+      new ResourceReference(resource, is_array, dest_op, begin_expr, end_expr);
   return std::optional<ResourceReference *>(ref);
 }
 
@@ -1574,9 +1585,17 @@
   }
   if (ctx->source != nullptr) {
     int instance = 0;
-    for (auto *ident : ctx->source->IDENT()) {
-      std::string name = ident->getText();
-      child->opcode()->AppendSourceOpName(name);
+    for (auto *source_op : ctx->source->source_operand()) {
+      std::string name;
+      bool is_array;
+      if (source_op->source != nullptr) {
+        name = source_op->source->getText();
+        is_array = false;
+      } else {
+        name = source_op->array_source->getText();
+        is_array = true;
+      }
+      child->opcode()->AppendSourceOp(name, is_array);
       parent->opcode()->op_locator_map().insert(
           std::make_pair(name, OperandLocator(op_spec_number, 's', instance)));
       instance++;
@@ -1585,7 +1604,15 @@
   if (ctx->dest_list() != nullptr) {
     int instance = 0;
     for (auto *dest_op : ctx->dest_list()->dest_operand()) {
-      std::string ident = dest_op->dest->getText();
+      std::string ident;
+      bool is_array;
+      if (dest_op->dest != nullptr) {
+        ident = dest_op->dest->getText();
+        is_array = false;
+      } else {
+        ident = dest_op->array_dest->getText();
+        is_array = true;
+      }
       // The latency of the destination operand is either specified by an
       // expression, by '*' (wildcard), or omitted, in which case it
       // defaults to 1.
@@ -1593,14 +1620,15 @@
         context_file_map_.insert(
             {dest_op->expression(), context_file_map_.at(slot->ctx())});
         child->opcode()->AppendDestOp(
-            ident, VisitExpression(dest_op->expression(), slot, child));
+            ident, is_array,
+            VisitExpression(dest_op->expression(), slot, child));
       } else if (dest_op->wildcard != nullptr) {
-        child->opcode()->AppendDestOp(ident);
+        child->opcode()->AppendDestOp(ident, is_array);
       } else if (slot->default_latency() != nullptr) {
-        child->opcode()->AppendDestOp(ident,
+        child->opcode()->AppendDestOp(ident, is_array,
                                       slot->default_latency()->DeepCopy());
       } else {
-        child->opcode()->AppendDestOp(ident, new TemplateConstant(1));
+        child->opcode()->AppendDestOp(ident, is_array, new TemplateConstant(1));
       }
       parent->opcode()->op_locator_map().insert(
           std::make_pair(ident, OperandLocator(op_spec_number, 'd', instance)));
@@ -2146,32 +2174,54 @@
   absl::StrAppend(
       &output, "  virtual ResourceOperandInterface *GetSimpleResourceOperand",
       "(SlotEnum slot, int entry, OpcodeEnum opcode, SimpleResourceVector "
-      "&resource_vec, int end) = 0;\n");
+      "&resource_vec, int end) { return nullptr;}\n");
   absl::StrAppend(
-      &output, "  virtual ResourceOperandInterface *GetComplexResourceOperand",
+      &output,
+      "  virtual ResourceOperandInterface * "
+      "GetComplexResourceOperand",
       "(SlotEnum slot, int entry, OpcodeEnum opcode, ComplexResourceEnum "
-      "resource_op, int begin, int end) = 0;\n");
+      "resource_op, int begin, int end) { return {}; }\n");
+  absl::StrAppend(
+      &output,
+      "  virtual std::vector<ResourceOperandInterface *> "
+      "GetComplexResourceOperands",
+      "(SlotEnum slot, int entry, OpcodeEnum opcode, ComplexResourceEnum "
+      "resource_op, int begin, int end) { return {}; }\n");
   // For each operand type, declare the pure virtual method that returns the
   // given operand.
   absl::StrAppend(&output,
                   "  virtual PredicateOperandInterface *GetPredicate"
                   "(SlotEnum slot, int entry, OpcodeEnum opcode, PredOpEnum "
-                  "pred_op) = 0;\n");
+                  "pred_op) { return nullptr; }\n");
   absl::StrAppend(&output,
                   "  virtual SourceOperandInterface *GetSource"
                   "(SlotEnum slot, int entry, OpcodeEnum opcode, SourceOpEnum "
-                  "source_op, int source_no) = 0;\n");
+                  "source_op, int source_no) { return nullptr;}\n");
+  absl::StrAppend(
+      &output,
+      "  virtual std::vector<SourceOperandInterface *> GetSources"
+      "(SlotEnum slot, int entry, OpcodeEnum opcode, ListSourceOpEnum "
+      "list_source_op, int source_no) { return {};}\n");
   absl::StrAppend(&output,
                   "  virtual DestinationOperandInterface *GetDestination"
                   "(SlotEnum slot, int entry, OpcodeEnum opcode, "
-                  "DestOpEnum dest_op, int dest_no, int latency)"
-                  "= 0;\n");
+                  "DestOpEnum list_dest_op, int dest_no, int latency)"
+                  " { return nullptr; }\n");
+  absl::StrAppend(
+      &output,
+      "  virtual std::vector<DestinationOperandInterface *> GetDestinations"
+      "(SlotEnum slot, int entry, OpcodeEnum opcode, "
+      "ListDestOpEnum dest_op, int dest_no, const std::vector<int> &latency)"
+      " { return {}; };\n");
   // Destination operand latency getter for destination operands with '*'
   // as latency.
   absl::StrAppend(
       &output,
       "  virtual int GetLatency(SlotEnum slot, int entry, OpcodeEnum "
-      "opcode, DestOpEnum dest_op, int dest_no) = 0;\n");
+      "opcode, DestOpEnum dest_op, int dest_no) { return 0; };\n",
+      "  virtual std::vector<int> GetLatency(SlotEnum slot, int entry, "
+      "OpcodeEnum "
+      "opcode, ListDestOpEnum dest_op, int dest_no) { return {0}; }\n");
 
   absl::StrAppend(&output, "};\n\n");
   absl::StrAppend(
diff --git a/mpact/sim/decoder/opcode.cc b/mpact/sim/decoder/opcode.cc
index bdc6bcf..aaed476 100644
--- a/mpact/sim/decoder/opcode.cc
+++ b/mpact/sim/decoder/opcode.cc
@@ -15,7 +15,6 @@
 #include "mpact/sim/decoder/opcode.h"
 
 #include <functional>
-#include <new>
 #include <string>
 #include <utility>
 
@@ -45,19 +44,19 @@
   dest_op_map_.clear();
 }
 
-void Opcode::AppendSourceOpName(absl::string_view op_name) {
-  source_op_name_vec_.emplace_back(op_name);
+void Opcode::AppendSourceOp(absl::string_view op_name, bool is_array) {
+  source_op_vec_.emplace_back(std::string(op_name), is_array);
 }
 
-void Opcode::AppendDestOp(absl::string_view op_name) {
-  auto *op = new DestinationOperand(std::string(op_name));
+void Opcode::AppendDestOp(absl::string_view op_name, bool is_array) {
+  auto *op = new DestinationOperand(std::string(op_name), is_array);
   dest_op_vec_.push_back(op);
   dest_op_map_.insert(std::make_pair(std::string(op_name), op));
 }
 
-void Opcode::AppendDestOp(absl::string_view op_name,
+void Opcode::AppendDestOp(absl::string_view op_name, bool is_array,
                           TemplateExpression *expression) {
-  auto *op = new DestinationOperand(std::string(op_name), expression);
+  auto *op = new DestinationOperand(std::string(op_name), is_array, expression);
   dest_op_vec_.push_back(op);
   dest_op_map_.insert(std::make_pair(std::string(op_name), op));
 }
@@ -108,15 +107,15 @@
   new_opcode->set_instruction_size(opcode->instruction_size());
   new_opcode->predicate_op_name_ = opcode->predicate_op_name();
   new_opcode->op_locator_map_ = opcode->op_locator_map();
-  for (auto const &op_name : opcode->source_op_name_vec()) {
-    new_opcode->AppendSourceOpName(op_name);
+  for (auto const &src_op : opcode->source_op_vec()) {
+    new_opcode->AppendSourceOp(src_op.name, src_op.is_array);
   }
 
   // Copy destination operands, but evaluate any latencies using the template
   // instantiation arguments, in case those expressions use them.
   for (auto const *dest_op : opcode->dest_op_vec()) {
     if (dest_op->expression() == nullptr) {
-      new_opcode->AppendDestOp(dest_op->name());
+      new_opcode->AppendDestOp(dest_op->name(), dest_op->is_array());
     } else {
       // For each destination operand that has an expression, evaluate it in the
       // context of the passed in TemplateInstantiationArgs. This creates a copy
@@ -124,7 +123,8 @@
       // recursively folded into constant nodes.
       auto result = dest_op->expression()->Evaluate(args);
       if (result.ok()) {
-        new_opcode->AppendDestOp(dest_op->name(), result.value());
+        new_opcode->AppendDestOp(dest_op->name(), dest_op->is_array(),
+                                 result.value());
       } else {
         delete new_opcode;
         return absl::InternalError(absl::StrCat(
diff --git a/mpact/sim/decoder/opcode.h b/mpact/sim/decoder/opcode.h
index 2e51bc4..ad327aa 100644
--- a/mpact/sim/decoder/opcode.h
+++ b/mpact/sim/decoder/opcode.h
@@ -50,26 +50,31 @@
 class DestinationOperand {
  public:
   // Operand latency is defined by the expression.
-  DestinationOperand(std::string name, TemplateExpression *expression)
+  DestinationOperand(std::string name, bool is_array,
+                     TemplateExpression *expression)
       : name_(std::move(name)),
         pascal_case_name_(ToPascalCase(name_)),
-        expression_(expression) {}
+        expression_(expression),
+        is_array_(is_array) {}
   // Operand latency is a constant.
-  DestinationOperand(std::string name, int latency)
+  DestinationOperand(std::string name, bool is_array, int latency)
       : name_(std::move(name)),
         pascal_case_name_(ToPascalCase(name_)),
-        expression_(new TemplateConstant(latency)) {}
+        expression_(new TemplateConstant(latency)),
+        is_array_(is_array) {}
   // This constructor is used when the destination operand latency is specified
   // as '*' - meaning that it will be computed at the time of decode.
-  explicit DestinationOperand(std::string name)
+  explicit DestinationOperand(std::string name, bool is_array)
       : name_(std::move(name)),
         pascal_case_name_(ToPascalCase(name_)),
-        expression_(nullptr) {}
+        expression_(nullptr),
+        is_array_(is_array) {}
   ~DestinationOperand() { delete expression_; }
 
   const std::string &name() const { return name_; }
   const std::string &pascal_case_name() const { return pascal_case_name_; }
   TemplateExpression *expression() const { return expression_; }
+  bool is_array() const { return is_array_; }
   bool HasLatency() const { return expression_ != nullptr; }
   absl::StatusOr<int> GetLatency() const {
     if (expression_ == nullptr) return -1;
@@ -90,6 +95,14 @@
   std::string name_;
   std::string pascal_case_name_;
   TemplateExpression *expression_;
+  bool is_array_ = false;
+};
+
+struct SourceOperand {
+  std::string name;
+  bool is_array;
+  SourceOperand(std::string name_, bool is_array_)
+      : name(std::move(name_)), is_array(is_array_) {}
 };
 
 // This struct is used to specify the location of an operand within an
@@ -145,18 +158,22 @@
 
 struct ResourceReference {
   Resource *resource;
+  bool is_array;
   DestinationOperand *dest_op;
   TemplateExpression *begin_expression;
   TemplateExpression *end_expression;
-  ResourceReference(Resource *resource_, DestinationOperand *dest_op_,
+  ResourceReference(Resource *resource_, bool is_array_,
+                    DestinationOperand *dest_op_,
                     TemplateExpression *begin_expr_,
                     TemplateExpression *end_expr_)
       : resource(resource_),
+        is_array(is_array_),
         dest_op(dest_op_),
         begin_expression(begin_expr_),
         end_expression(end_expr_) {}
   ResourceReference(const ResourceReference &rhs) {
     resource = rhs.resource;
+    is_array = rhs.is_array;
     dest_op = rhs.dest_op;
     begin_expression = rhs.begin_expression->DeepCopy();
     end_expression = rhs.end_expression->DeepCopy();
@@ -187,9 +204,10 @@
   // to get the Predicate, Source and Destination operand interfaces (defined
   // in .../sim/generic/operand_interfaces.h. The implementation of these
   // methods will be left to the user of this generator tool.
-  void AppendSourceOpName(absl::string_view op_name);
-  void AppendDestOp(absl::string_view op_name, TemplateExpression *expression);
-  void AppendDestOp(absl::string_view op_name);
+  void AppendSourceOp(absl::string_view op_name, bool is_array);
+  void AppendDestOp(absl::string_view op_name, bool is_array,
+                    TemplateExpression *expression);
+  void AppendDestOp(absl::string_view op_name, bool is_array);
   DestinationOperand *GetDestOp(absl::string_view op_name);
   // Append child opcode specification.
   void AppendChild(Opcode *op) { child_ = op; }
@@ -210,8 +228,8 @@
   void set_predicate_op_name(absl::string_view op_name) {
     predicate_op_name_ = op_name;
   }
-  const std::vector<std::string> &source_op_name_vec() const {
-    return source_op_name_vec_;
+  const std::vector<SourceOperand> &source_op_vec() const {
+    return source_op_vec_;
   }
   const std::vector<DestinationOperand *> &dest_op_vec() const {
     return dest_op_vec_;
@@ -226,7 +244,7 @@
   Opcode *child_ = nullptr;
   Opcode *parent_ = nullptr;
   std::string predicate_op_name_;
-  std::vector<std::string> source_op_name_vec_;
+  std::vector<SourceOperand> source_op_vec_;
   std::vector<DestinationOperand *> dest_op_vec_;
   absl::flat_hash_map<std::string, DestinationOperand *> dest_op_map_;
   std::string name_;
diff --git a/mpact/sim/decoder/resource.h b/mpact/sim/decoder/resource.h
index 68364e6..6567b15 100644
--- a/mpact/sim/decoder/resource.h
+++ b/mpact/sim/decoder/resource.h
@@ -47,6 +47,8 @@
   void set_is_simple(bool value) { is_simple_ = value; }
   bool is_multi_valued() const { return is_multi_valued_; }
   void set_is_multi_valued(bool value) { is_multi_valued_ = value; }
+  bool is_array() const { return is_array_; }
+  void set_is_array(bool value) { is_array_ = value; }
 
   const std::string &name() const { return name_; }
   const std::string &pascal_name() const { return pascal_name_; }
@@ -57,6 +59,7 @@
   std::string pascal_name_;
   bool is_multi_valued_ = false;
   bool is_simple_ = true;
+  bool is_array_ = false;
 };
 
 // Resource factory class. This is used so that there's a single registry of
diff --git a/mpact/sim/decoder/slot.cc b/mpact/sim/decoder/slot.cc
index 941cb9c..2b5edf0 100644
--- a/mpact/sim/decoder/slot.cc
+++ b/mpact/sim/decoder/slot.cc
@@ -383,12 +383,18 @@
   if (!simple_refs.empty()) {
     // First gather the resource references into a single vector, then request
     // the resource operands for all the resource references in that vector.
-    std::string sep = "";
     absl::StrAppend(&output,
                     "    std::vector<SimpleResourceEnum> hold_vec = {");
     for (auto const *simple : simple_refs) {
-      absl::StrAppend(&output, sep, "\n        SimpleResourceEnum::k",
-                      simple->resource->pascal_name(), ", ");
+      std::string resource_name;
+      if (simple->is_array) {
+        resource_name = absl::StrCat("SimpleResourceEnum::k",
+                                     simple->resource->pascal_name());
+      } else {
+        resource_name = absl::StrCat("SimpleResourceEnum::k",
+                                     simple->resource->pascal_name());
+      }
+      absl::StrAppend(&output, "\n        ", resource_name, ", ");
     }
     absl::StrAppend(&output,
                     "};\n\n"
@@ -416,15 +422,27 @@
           &output, "#error Unable to get value of begin or end expression\n");
       continue;
     }
-    absl::StrAppend(&output,
-                    "    res_op = enc->GetComplexResourceOperand(slot, entry, ",
-                    opcode_enum, ", ComplexResourceEnum::k",
-                    complex->resource->pascal_name(), ", ");
-    absl::StrAppend(&output, *begin, ", ", *end, ");\n");
-    absl::StrAppend(&output,
-                    "    if (res_op != nullptr) {\n"
-                    "      inst->AppendResourceHold(res_op);\n"
-                    "    }\n");
+    if (complex->is_array) {
+      absl::StrAppend(
+          &output,
+          "    auto res_op_vec = enc->GetComplexResourceOperands(slot, entry, ",
+          opcode_enum, ", ListComplexResourceEnum::k",
+          complex->resource->pascal_name(), *begin, ", ", *end, ");\n");
+      absl::StrAppend(&output,
+                      "    for (auto res_op : res_op_vec) {\n"
+                      "      inst->AppendResourceHold(res_op);\n"
+                      "    }\n");
+    } else {
+      absl::StrAppend(
+          &output, "    res_op = enc->GetComplexResourceOperand(slot, entry, ",
+          opcode_enum, ", ComplexResourceEnum::k",
+          complex->resource->pascal_name(), ", ");
+      absl::StrAppend(&output, *begin, ", ", *end, ");\n");
+      absl::StrAppend(&output,
+                      "    if (res_op != nullptr) {\n"
+                      "      inst->AppendResourceHold(res_op);\n"
+                      "    }\n");
+    }
   }
 
   // Get all the simple resources that need to be reserved, then all the complex
@@ -473,8 +491,15 @@
       for (auto iter = latency_map.lower_bound(latency);
            iter != latency_map.upper_bound(latency); ++iter) {
         auto *simple = iter->second;
-        absl::StrAppend(&output, sep, "\n        SimpleResourceEnum::k",
-                        simple->resource->pascal_name(), ",");
+        std::string resource_name;
+        if (simple->is_array) {
+          resource_name = absl::StrCat("SimpleResourceEnum::k",
+                                       simple->resource->pascal_name());
+        } else {
+          resource_name = absl::StrCat("SimpleResourceEnum::k",
+                                       simple->resource->pascal_name());
+        }
+        absl::StrAppend(&output, sep, "\n        ", resource_name, ",");
       }
       absl::StrAppend(
           &output,
@@ -504,16 +529,29 @@
       // Get the integer values from the begin and end expression values.
       int *begin = std::get_if<int>(&begin_value.value());
       int *end = std::get_if<int>(&end_value.value());
-      absl::StrAppend(
-          &output,
-          "    res_op = enc->GetComplexResourceOperand(ComplexResourceEnum::k",
-          complex->resource->pascal_name(), ", ResourceArgumentEnum::k");
-      absl::StrAppend(&output, "None, slot, entry, ", *begin, ", ", *end,
-                      ");\n");
-      absl::StrAppend(&output,
-                      "    if (res != nullptr) {\n"
-                      "      inst->AppendResourceAcquire(res_op);\n"
-                      "    }\n");
+      if (complex->is_array) {
+        absl::StrAppend(&output,
+                        "    auto res_op_vec = "
+                        "enc->GetComplexResourceOperands(slot, entry, ",
+                        opcode_enum, ", ListComplexResourceEnum::k",
+                        complex->resource->pascal_name(), *begin, ", ", *end,
+                        ");\n");
+        absl::StrAppend(&output,
+                        "    for (auto res_op : res_op_vec) {\n"
+                        "      inst->AppendResourceHold(res_op);\n"
+                        "    }\n");
+      } else {
+        absl::StrAppend(
+            &output,
+            "    res_op = enc->GetComplexResourceOperand(slot, entry, ",
+            opcode_enum, ", ComplexResourceEnum::k",
+            complex->resource->pascal_name(), ", ");
+        absl::StrAppend(&output, *begin, ", ", *end, ");\n");
+        absl::StrAppend(&output,
+                        "    if (res_op != nullptr) {\n"
+                        "      inst->AppendResourceHold(res_op);\n"
+                        "    }\n");
+      }
     }
   }
   absl::StrAppend(&output, "  };\n\n");
@@ -586,39 +624,76 @@
       }
       // Generate code to set the instruction's source operands.
       int source_no = 0;
-      for (const auto &src_name : inst->opcode()->source_op_name_vec()) {
-        std::string src_op_enum =
-            absl::StrCat("SourceOpEnum::k", ToPascalCase(src_name));
-        absl::StrAppend(&output, "        inst->AppendSource(enc->GetSource",
-                        "(slot_, entry, ", opcode_enum, ", ", src_op_enum, ", ",
-                        source_no++, "));\n");
+      for (const auto &src_op : inst->opcode()->source_op_vec()) {
+        // If the source operand is an array, then we need to iterate over the
+        // vector of operands that GetSources returns.
+        if (src_op.is_array) {
+          std::string src_op_enum =
+              absl::StrCat("ListSourceOpEnum::k", ToPascalCase(src_op.name));
+          absl::StrAppend(
+              &output,
+              "      {\n"
+              "         auto vec = enc->GetSources",
+              "(slot_, entry, ", opcode_enum, ", ", src_op_enum, ", ",
+              source_no,
+              ");\n"
+              "         for (auto *op : vec) inst->AppendSource(op);\n"
+              "      }\n");
+        } else {
+          std::string src_op_enum =
+              absl::StrCat("SourceOpEnum::k", ToPascalCase(src_op.name));
+          absl::StrAppend(&output, "        inst->AppendSource(enc->GetSource",
+                          "(slot_, entry, ", opcode_enum, ", ", src_op_enum,
+                          ", ", source_no++, "));\n");
+        }
       }
       // Generate code to set the instruction's destination operands.
       int dest_no = 0;
       for (auto const *dst_op : inst->opcode()->dest_op_vec()) {
-        std::string dest_op_enum =
-            absl::StrCat("DestOpEnum::k", dst_op->pascal_case_name());
+        std::string dest_op_enum;
+        if (dst_op->is_array()) {
+          dest_op_enum =
+              absl::StrCat("ListDestOpEnum::k", dst_op->pascal_case_name());
+        } else {
+          dest_op_enum =
+              absl::StrCat("DestOpEnum::k", dst_op->pascal_case_name());
+        }
+        std::string latency;
         if (dst_op->expression() == nullptr) {
+          latency = absl::StrCat("enc->GetLatency(slot_, entry, ", opcode_enum,
+                                 ", ", dest_op_enum, ", ", dest_no, ")");
+        } else {
+          auto result = dst_op->GetLatency();
+          if (!result.ok()) {
+            absl::StrAppend(&output,
+                            "#error \"Failed to get latency for operand '",
+                            dst_op->name(), "'\"");
+            dest_no++;
+            continue;
+          }
+          latency = absl::StrCat(result.value());
+          // If the operand is an array, then the latency is a vector.
+          if (dst_op->is_array()) {
+            latency = absl::StrCat("{", latency, "}");
+          }
+        }
+        if (dst_op->is_array()) {
+          absl::StrAppend(
+              &output,
+              "      {\n"
+              "         auto vec = enc->GetDestinations",
+              "(slot_, entry, ", opcode_enum, ", ", dest_op_enum, ", ", dest_no,
+              ", ", latency,
+              ");\n"
+              "         for (auto *op : vec) inst->AppendDestination(op);\n"
+              "      }");
+        } else {
           absl::StrAppend(
               &output, "        inst->AppendDestination(enc->GetDestination(",
               "slot_, entry, ", opcode_enum, ", ", dest_op_enum, ", ", dest_no,
-              ", enc->GetLatency(slot_, entry, ", opcode_enum, ", ",
-              dest_op_enum, " , ", dest_no, ")));\n");
+              ", ", latency, "));\n");
           dest_no++;
-          continue;
         }
-        auto result = dst_op->GetLatency();
-        if (!result.ok()) {
-          absl::StrAppend(&output,
-                          "#error \"Failed to get latency for operand '",
-                          dst_op->name(), "'\"");
-          dest_no++;
-          continue;
-        }
-        absl::StrAppend(&output,
-                        "        inst->AppendDestination(enc->GetDestination",
-                        "(slot_, entry, ", opcode_enum, ", ", dest_op_enum,
-                        ", ", dest_no, ", ", result.value(), "));\n");
         dest_no++;
       }
       absl::StrAppend(&output, "      });\n\n");
diff --git a/mpact/sim/decoder/slot.h b/mpact/sim/decoder/slot.h
index 33fb85b..888b584 100644
--- a/mpact/sim/decoder/slot.h
+++ b/mpact/sim/decoder/slot.h
@@ -135,7 +135,9 @@
   absl::btree_map<std::string, ResourceDetailsCtx *> &resource_spec_map() {
     return resource_spec_map_;
   }
-
+  absl::btree_map<std::string, IdentListCtx *> &resource_array_ref_map() {
+    return resource_array_ref_map_;
+  }
   const absl::btree_map<std::string, TemplateExpression *> &attribute_map() {
     return attribute_map_;
   }
@@ -194,6 +196,7 @@
   absl::flat_hash_map<std::string, TemplateExpression *> constant_map_;
   // Named resource specifiers.
   absl::btree_map<std::string, ResourceDetailsCtx *> resource_spec_map_;
+  absl::btree_map<std::string, IdentListCtx *> resource_array_ref_map_;
   // Default instruction attributes.
   absl::btree_map<std::string, TemplateExpression *> attribute_map_;
 };
diff --git a/mpact/sim/decoder/test/BUILD b/mpact/sim/decoder/test/BUILD
index cee5d5a..bcef866 100644
--- a/mpact/sim/decoder/test/BUILD
+++ b/mpact/sim/decoder/test/BUILD
@@ -14,7 +14,7 @@
 
 # This contains the test projects for the decoder.
 
-load("//mpact/sim/decoder:mpact_sim_isa.bzl", "mpact_cc_library", "mpact_cc_test", "mpact_isa_decoder", "mpact_proto_fmt_decoder")
+load("//mpact/sim/decoder:mpact_sim_isa.bzl", "mpact_bin_fmt_decoder", "mpact_cc_library", "mpact_cc_test", "mpact_isa_decoder", "mpact_proto_fmt_decoder")
 
 package(
     default_applicable_licenses = ["//:license"],
@@ -86,6 +86,7 @@
     deps = [
         "//mpact/sim/decoder:isa_parser",
         "@com_google_absl//absl/memory",
+        "@com_google_absl//absl/status",
         "@com_google_absl//absl/status:statusor",
         "@com_google_absl//absl/strings",
         "@com_google_googletest//:gtest",
@@ -490,3 +491,82 @@
         "@com_google_googletest//:gtest_main",
     ],
 )
+
+cc_library(
+    name = "push_pop_instructions",
+    srcs = [
+        "push_pop.cc",
+    ],
+    hdrs = [
+        "push_pop.h",
+    ],
+    deps = [
+        "//mpact/sim/generic:instruction",
+    ],
+)
+
+cc_library(
+    name = "push_pop_decoder",
+    srcs = [
+        "push_pop_decoder.cc",
+        "push_pop_encoding.cc",
+    ],
+    hdrs = [
+        "push_pop_decoder.h",
+        "push_pop_encoding.h",
+    ],
+    deps = [
+        ":push_pop_bin_fmt",
+        ":push_pop_isa",
+        "//mpact/sim/generic:arch_state",
+        "//mpact/sim/generic:core",
+        "//mpact/sim/generic:instruction",
+        "//mpact/sim/generic:type_helpers",
+        "//mpact/sim/util/memory",
+        "@com_google_absl//absl/container:flat_hash_map",
+        "@com_google_absl//absl/functional:any_invocable",
+        "@com_google_absl//absl/log",
+    ],
+)
+
+mpact_isa_decoder(
+    name = "push_pop_isa",
+    src = "push_pop.isa",
+    includes = [],
+    isa_name = "PushPopInst",
+    prefix = "push_pop_inst",
+    deps = [
+        ":push_pop_instructions",
+        "@com_google_absl//absl/functional:bind_front",
+    ],
+)
+
+mpact_bin_fmt_decoder(
+    name = "push_pop_bin_fmt",
+    src = "push_pop.bin_fmt",
+    decoder_name = "PushPopInst",
+    includes = [],
+    prefix = "push_pop_inst",
+    deps = [
+        ":push_pop_isa",
+    ],
+)
+
+mpact_cc_test(
+    name = "array_operand_test",
+    size = "small",
+    srcs = [
+        "array_operand_test.cc",
+    ],
+    deps = [
+        ":push_pop_decoder",
+        ":push_pop_isa",
+        "//mpact/sim/generic:arch_state",
+        "//mpact/sim/generic:core",
+        "//mpact/sim/util/memory",
+        "@com_google_absl//absl/log:check",
+        "@com_google_absl//absl/strings",
+        "@com_google_googletest//:gtest",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
diff --git a/mpact/sim/decoder/test/array_operand_test.cc b/mpact/sim/decoder/test/array_operand_test.cc
new file mode 100644
index 0000000..c0bdb3f
--- /dev/null
+++ b/mpact/sim/decoder/test/array_operand_test.cc
@@ -0,0 +1,148 @@
+#include <sys/types.h>
+
+#include <cstdint>
+#include <memory>
+
+#include "absl/log/check.h"
+#include "absl/strings/str_cat.h"
+#include "googlemock/include/gmock/gmock.h"  // IWYU pragma: keep
+#include "googletest/include/gtest/gtest.h"
+#include "mpact/sim/decoder/test/push_pop_decoder.h"
+#include "mpact/sim/decoder/test/push_pop_inst_enums.h"
+#include "mpact/sim/generic/arch_state.h"
+#include "mpact/sim/generic/data_buffer.h"
+#include "mpact/sim/generic/register.h"
+#include "mpact/sim/util/memory/flat_demand_memory.h"
+
+// This file tests that the "array" or "list" valued operands used in the .isa
+// description is handled correctly by the decoder. This test case uses the
+// push/pop isa and decoder.
+
+namespace {
+
+using ::mpact::sim::decoder::test::OpcodeEnum;
+using ::mpact::sim::decoder::test::PushPopDecoder;
+using ::mpact::sim::generic::DataBuffer;
+using ::mpact::sim::util::FlatDemandMemory;
+
+using TestRegister = ::mpact::sim::generic::Register<uint32_t>;
+
+constexpr uint64_t kBase = 0x1000;
+
+// A test state, since ArchState cannot be instantiated directly.
+class TestState : public ::mpact::sim::generic::ArchState {
+ public:
+  TestState() : ::mpact::sim::generic::ArchState("TestState") {}
+};
+
+// Helper functions to generate encoding push and pop instructions with
+// different rlist and spimm values.
+static uint16_t GeneratePushInstruction(uint8_t rlist, uint8_t spimm) {
+  uint16_t inst = 0b101'11000'0000'00'10;
+  return inst | ((rlist & 0xf) << 4) | ((spimm & 0x3) << 2);
+}
+
+static uint16_t GeneratePopInstruction(uint8_t rlist, uint8_t spimm) {
+  uint16_t inst = 0b101'11010'0000'00'10;
+  return inst | ((rlist & 0xf) << 4) | ((spimm & 0x3) << 2);
+}
+
+// Test fixture. This adds registers to the state and writes instructions to
+// memory.
+class ArrayOperandTest : public ::testing::Test {
+ protected:
+  ArrayOperandTest() {
+    state_ = std::make_unique<TestState>();
+    memory_ = std::make_unique<FlatDemandMemory>();
+    decoder_ = std::make_unique<PushPopDecoder>(state_.get(), memory_.get());
+    // Add registers to the state.
+    for (int i = 1; i < 32; i++) {
+      state_->AddRegister(new TestRegister(state_.get(), absl::StrCat("x", i)));
+    }
+    // Write instructions to memory - 16 each of pushes and pops. The first
+    // 4 of each are illegal instructions since rlist < 4.
+    DataBuffer *db = state_->db_factory()->Allocate<uint16_t>(1);
+    for (int i = 0; i < 16; i++) {
+      db->Set<uint16_t>(0, GeneratePushInstruction(i, i & 0x3));
+      memory_->Store(kBase + i * db->size<uint8_t>(), db);
+    }
+    for (int i = 0; i < 16; i++) {
+      db->Set<uint16_t>(0, GeneratePopInstruction(i, i & 0x3));
+      memory_->Store(kBase + 16 * db->size<uint8_t>() + i * db->size<uint8_t>(),
+                     db);
+    }
+    db->DecRef();
+  }
+
+  std::unique_ptr<TestState> state_;
+  std::unique_ptr<FlatDemandMemory> memory_;
+  std::unique_ptr<PushPopDecoder> decoder_;
+};
+
+// Verify that the push instructions are decoded correctly.
+TEST_F(ArrayOperandTest, PushInstructionDecoding) {
+  for (int i = 0; i < 16; i++) {
+    auto *inst = decoder_->DecodeInstruction(kBase + i * 2);
+    CHECK_NE(inst, nullptr);
+    if (i < 4) {
+      EXPECT_EQ(inst->opcode(), *OpcodeEnum::kNone);
+    } else {
+      EXPECT_EQ(inst->opcode(), *OpcodeEnum::kPush);
+    }
+    inst->DecRef();
+  }
+}
+
+// Verify that the pop instructions are decoded correctly.
+TEST_F(ArrayOperandTest, PopInstructionDecoding) {
+  for (int i = 0; i < 16; i++) {
+    auto *inst = decoder_->DecodeInstruction(kBase + 16 * 2 + i * 2);
+    CHECK_NE(inst, nullptr);
+    if (i < 4) {
+      EXPECT_EQ(inst->opcode(), *OpcodeEnum::kNone);
+    } else {
+      EXPECT_EQ(inst->opcode(), *OpcodeEnum::kPop);
+    }
+    inst->DecRef();
+  }
+}
+
+// Verify that the push instructions have the correct number of source and
+// destination operands.
+TEST_F(ArrayOperandTest, PushOperands) {
+  for (int i = 4; i < 16; i++) {
+    auto *inst = decoder_->DecodeInstruction(kBase + i * 2);
+    CHECK_NE(inst, nullptr);
+    EXPECT_EQ(inst->opcode(), *OpcodeEnum::kPush)
+        << inst->AsString() << " for instruction " << i;
+    // Push instructions have 3 source operands (x2, spimm6, rlist) in addition
+    // to the list of registers to be pushed. The number of registers pushed
+    // is determined by the rlist field, 4 -> 1 register, 5 -> 2 registers,
+    // etc., up to 14 -> 11 registers. Then for 15 -> 13 registers.
+    EXPECT_EQ(inst->SourcesSize(), 3 + (i - 3) + (i == 15 ? 1 : 0));
+    // There should only be a single destination operand, x2.
+    EXPECT_EQ(inst->DestinationsSize(), 1);
+    inst->DecRef();
+  }
+}
+
+// Verify that the pop instructions have the correct number of source and
+// destination operands.
+TEST_F(ArrayOperandTest, PopOperands) {
+  for (int i = 4; i < 16; i++) {
+    auto *inst = decoder_->DecodeInstruction(kBase + 16 * 2 + i * 2);
+    CHECK_NE(inst, nullptr);
+    EXPECT_EQ(inst->opcode(), *OpcodeEnum::kPop)
+        << inst->AsString() << " for instruction " << i;
+    // Pop instructions have 3 source operands (x2, spimm6, rlist).
+    EXPECT_EQ(inst->SourcesSize(), 3);
+    // The number of destination operands is x2 and the list of registers to be
+    // popped from the stack. The number of registers  is determined by the
+    // rlist field, 4 -> 1 register, 5 -> 2 registers, etc., up to 14 -> 11
+    // registers. Then for 15 -> 13 registers.
+    EXPECT_EQ(inst->DestinationsSize(), 1 + (i - 3) + (i == 15 ? 1 : 0));
+    inst->DecRef();
+  }
+}
+
+}  // namespace
diff --git a/mpact/sim/decoder/test/opcode_test.cc b/mpact/sim/decoder/test/opcode_test.cc
index d0a234f..fba903c 100644
--- a/mpact/sim/decoder/test/opcode_test.cc
+++ b/mpact/sim/decoder/test/opcode_test.cc
@@ -17,8 +17,10 @@
 #include <memory>
 #include <string>
 
+#include "absl/status/status.h"
 #include "absl/status/statusor.h"
-#include "googlemock/include/gmock/gmock.h"
+#include "absl/strings/str_cat.h"
+#include "googlemock/include/gmock/gmock.h"  // IWYU pragma: keep
 #include "googletest/include/gtest/gtest.h"
 #include "mpact/sim/decoder/instruction_set.h"
 
@@ -57,7 +59,7 @@
   EXPECT_STREQ(opcode_->name().c_str(), kOpcodeName0);
   EXPECT_EQ(opcode_->value(), 1);
   EXPECT_STREQ(opcode_->predicate_op_name().c_str(), "");
-  EXPECT_EQ(opcode_->source_op_name_vec().size(), 0);
+  EXPECT_EQ(opcode_->source_op_vec().size(), 0);
   EXPECT_EQ(opcode_->dest_op_vec().size(), 0);
 }
 
@@ -88,9 +90,9 @@
 TEST_F(OpcodeTest, SourceOperandNames) {
   for (int indx = 0; indx < 3; indx++) {
     std::string source_op_name = absl::StrCat("SourceOp", indx);
-    opcode_->AppendSourceOpName(source_op_name);
-    EXPECT_EQ(opcode_->source_op_name_vec().size(), indx + 1);
-    EXPECT_STREQ(opcode_->source_op_name_vec()[indx].c_str(),
+    opcode_->AppendSourceOp(source_op_name, /*is_array=*/false);
+    EXPECT_EQ(opcode_->source_op_vec().size(), indx + 1);
+    EXPECT_STREQ(opcode_->source_op_vec()[indx].name.c_str(),
                  source_op_name.c_str());
   }
 }
@@ -100,10 +102,10 @@
   for (int indx = 0; indx < 2; indx++) {
     std::string dest_op_name = absl::StrCat("DestOp", indx);
     if (indx == 0) {
-      opcode_->AppendDestOp(dest_op_name);
+      opcode_->AppendDestOp(dest_op_name, /*is_array=*/false);
     } else if (indx == 1) {
       // Using nullptr - the value isn't checked upon append.
-      opcode_->AppendDestOp(dest_op_name, nullptr);
+      opcode_->AppendDestOp(dest_op_name, /*is_array=*/false, nullptr);
     }
     EXPECT_EQ(opcode_->dest_op_vec().size(), indx + 1);
     EXPECT_STREQ(opcode_->dest_op_vec()[indx]->name().c_str(),
diff --git a/mpact/sim/decoder/test/push_pop.bin_fmt b/mpact/sim/decoder/test/push_pop.bin_fmt
new file mode 100644
index 0000000..21642f2
--- /dev/null
+++ b/mpact/sim/decoder/test/push_pop.bin_fmt
@@ -0,0 +1,47 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// This file defines the push and pop instructions for a test ISA to test
+// "array" or "list" operands.
+
+decoder PushPopInst {
+  namespace mpact::sim::decoder::test;
+  opcode_enum = "OpcodeEnum";
+  includes {
+    #include "mpact/sim/decoder/test/push_pop_inst_decoder.h"
+  }
+  PushPop;
+}
+
+format Inst16Format[16] {
+  fields:
+    unsigned func3[3];
+    unsigned bits[11];
+    unsigned op[2];
+}
+
+format PType[16] : Inst16Format {
+  fields:
+    unsigned func8[8];
+    unsigned rlist[4];
+    unsigned spimm[2];
+    unsigned op[2];
+  overlays:
+    unsigned spimm6[6] = spimm, 0b0000;
+}
+
+instruction group PushPop[16] : Inst16Format {
+  push : PType : func8 == 0b101'11000, rlist > 3, op == 0b10;
+  pop  : PType : func8 == 0b101'11010, rlist > 3, op == 0b10;
+}
\ No newline at end of file
diff --git a/mpact/sim/decoder/test/push_pop.cc b/mpact/sim/decoder/test/push_pop.cc
new file mode 100644
index 0000000..b7ac9f7
--- /dev/null
+++ b/mpact/sim/decoder/test/push_pop.cc
@@ -0,0 +1,33 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     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/test/push_pop.h"
+
+#include "mpact/sim/generic/instruction.h"
+
+namespace mpact {
+namespace sim {
+namespace decoder {
+namespace test {
+
+using ::mpact::sim::generic::Instruction;
+
+void Illegal(const Instruction *inst) { /*empty*/ }
+void Push(const Instruction *inst) { /*empty*/ }
+void Pop(const Instruction *inst) { /*empty*/ }
+
+}  // namespace test
+}  // namespace decoder
+}  // namespace sim
+}  // namespace mpact
diff --git a/mpact/sim/decoder/test/push_pop.h b/mpact/sim/decoder/test/push_pop.h
new file mode 100644
index 0000000..01523d1
--- /dev/null
+++ b/mpact/sim/decoder/test/push_pop.h
@@ -0,0 +1,36 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef MPACT_SIM_DECODER_TEST_PUSH_POP_H_
+#define MPACT_SIM_DECODER_TEST_PUSH_POP_H_
+
+#include "mpact/sim/generic/instruction.h"
+
+namespace mpact {
+namespace sim {
+namespace decoder {
+namespace test {
+
+using ::mpact::sim::generic::Instruction;
+
+void Illegal(const Instruction *inst);
+void Push(const Instruction *inst);
+void Pop(const Instruction *inst);
+
+}  // namespace test
+}  // namespace decoder
+}  // namespace sim
+}  // namespace mpact
+
+#endif  // MPACT_SIM_DECODER_TEST_PUSH_POP_H_
diff --git a/mpact/sim/decoder/test/push_pop.isa b/mpact/sim/decoder/test/push_pop.isa
new file mode 100644
index 0000000..de79edf
--- /dev/null
+++ b/mpact/sim/decoder/test/push_pop.isa
@@ -0,0 +1,42 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// This file defines the isa for the push/pop example decoder.
+
+disasm widths = {-18};
+
+isa PushPopInst {
+  namespace mpact::sim::decoder::test;
+  slots { push_pop_inst; }
+}
+
+slot push_pop_inst {
+  includes {
+    #include "mpact/sim/decoder/test/push_pop.h"
+  }
+  default size = 2;
+  default opcode =
+    disasm: "IllegalInstruction",
+    semfunc: "&Illegal";
+  opcodes {
+    push{ : x2, spimm6, rlist, [rlist] : x2 },
+      resources: {x2, [rlist] : x2},
+      disasm: "push",
+      semfunc: "&Push";
+    pop{ : x2, spimm6, rlist : x2, [rlist] },
+      resources: { x2 : x2, [rlist]},
+      disasm: "pop",
+      semfunc: "&Pop";
+  }
+}
\ No newline at end of file
diff --git a/mpact/sim/decoder/test/push_pop_decoder.cc b/mpact/sim/decoder/test/push_pop_decoder.cc
new file mode 100644
index 0000000..d2b3596
--- /dev/null
+++ b/mpact/sim/decoder/test/push_pop_decoder.cc
@@ -0,0 +1,57 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     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/test/push_pop_decoder.h"
+
+#include <sys/types.h>
+
+#include <cstdint>
+
+#include "mpact/sim/decoder/test/push_pop_encoding.h"
+#include "mpact/sim/decoder/test/push_pop_inst_decoder.h"
+#include "mpact/sim/decoder/test/push_pop_inst_enums.h"
+#include "mpact/sim/generic/instruction.h"
+#include "mpact/sim/util/memory/memory_interface.h"
+
+namespace mpact::sim::decoder::test {
+
+using ::mpact::sim::generic::Instruction;
+using ::mpact::sim::util::MemoryInterface;
+
+PushPopDecoder::PushPopDecoder(ArchState *state, MemoryInterface *memory)
+    : state_(state), memory_(memory) {
+  push_pop_isa_factory_ = new PushPopIsaFactory();
+  push_pop_isa_ = new PushPopInstInstructionSet(state, push_pop_isa_factory_);
+  push_pop_encoding_ = new PushPopEncoding(state_);
+  inst_db_ = state_->db_factory()->Allocate<uint16_t>(1);
+}
+
+PushPopDecoder::~PushPopDecoder() {
+  inst_db_->DecRef();
+  delete push_pop_encoding_;
+  delete push_pop_isa_;
+  delete push_pop_isa_factory_;
+}
+
+Instruction *PushPopDecoder::DecodeInstruction(uint64_t address) {
+  memory_->Load(address, inst_db_, nullptr, nullptr);
+  uint16_t iword = inst_db_->Get<uint16_t>(0);
+  push_pop_encoding_->ParseInstruction(iword);
+  auto *instruction = push_pop_isa_->Decode(address, push_pop_encoding_);
+  instruction->set_opcode(
+      *push_pop_encoding_->GetOpcode(SlotEnum::kPushPopInst, 0));
+  return instruction;
+}
+
+}  // namespace mpact::sim::decoder::test
diff --git a/mpact/sim/decoder/test/push_pop_decoder.h b/mpact/sim/decoder/test/push_pop_decoder.h
new file mode 100644
index 0000000..821d362
--- /dev/null
+++ b/mpact/sim/decoder/test/push_pop_decoder.h
@@ -0,0 +1,73 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#ifndef MPACT_SIM_DECODER_TEST_PUSH_POP_DECODER_H_
+#define MPACT_SIM_DECODER_TEST_PUSH_POP_DECODER_H_
+
+#include <cstdint>
+#include <memory>
+
+#include "mpact/sim/decoder/test/push_pop_encoding.h"
+#include "mpact/sim/decoder/test/push_pop_inst_decoder.h"
+#include "mpact/sim/decoder/test/push_pop_inst_enums.h"
+#include "mpact/sim/generic/arch_state.h"
+#include "mpact/sim/generic/data_buffer.h"
+#include "mpact/sim/generic/decoder_interface.h"
+#include "mpact/sim/generic/instruction.h"
+#include "mpact/sim/generic/type_helpers.h"
+#include "mpact/sim/util/memory/memory_interface.h"
+
+// This file defines the decoder class for the push/pop isa test case.
+
+namespace mpact::sim::decoder::test {
+
+using ::mpact::sim::generic::operator*;  // NOLINT
+
+using ::mpact::sim::generic::ArchState;
+using ::mpact::sim::generic::DataBuffer;
+using ::mpact::sim::util::MemoryInterface;
+
+class PushPopIsaFactory : public PushPopInstInstructionSetFactory {
+ public:
+  std::unique_ptr<PushPopInstSlot> CreatePushPopInstSlot(
+      ArchState *state) override {
+    return std::make_unique<PushPopInstSlot>(state);
+  }
+};
+
+class PushPopDecoder : public generic::DecoderInterface {
+ public:
+  PushPopDecoder(ArchState *state, MemoryInterface *memory);
+  PushPopDecoder() = delete;
+  ~PushPopDecoder() override;
+
+  generic::Instruction *DecodeInstruction(uint64_t address) override;
+  int GetNumOpcodes() const override { return *OpcodeEnum::kPastMaxValue; }
+  const char *GetOpcodeName(int index) const override {
+    return kOpcodeNames[index];
+  }
+
+  PushPopEncoding *push_pop_encoding() const { return push_pop_encoding_; }
+
+ private:
+  ArchState *state_;
+  MemoryInterface *memory_;
+  PushPopEncoding *push_pop_encoding_;
+  PushPopIsaFactory *push_pop_isa_factory_;
+  PushPopInstInstructionSet *push_pop_isa_;
+  DataBuffer *inst_db_;
+};
+
+}  // namespace mpact::sim::decoder::test
+
+#endif  // MPACT_SIM_DECODER_TEST_PUSH_POP_DECODER_H_
diff --git a/mpact/sim/decoder/test/push_pop_encoding.cc b/mpact/sim/decoder/test/push_pop_encoding.cc
new file mode 100644
index 0000000..487e8c2
--- /dev/null
+++ b/mpact/sim/decoder/test/push_pop_encoding.cc
@@ -0,0 +1,232 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     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/test/push_pop_encoding.h"
+
+#include <cstdint>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "mpact/sim/decoder/test/push_pop_inst_bin_decoder.h"
+#include "mpact/sim/decoder/test/push_pop_inst_enums.h"
+#include "mpact/sim/generic/arch_state.h"
+#include "mpact/sim/generic/immediate_operand.h"
+#include "mpact/sim/generic/operand_interface.h"
+#include "mpact/sim/generic/register.h"
+#include "mpact/sim/generic/type_helpers.h"
+
+namespace mpact::sim::decoder::test {
+
+using ::mpact::sim::generic::operator*;  // NOLINT
+
+using ::mpact::sim::generic::ArchState;
+using ::mpact::sim::generic::ImmediateOperand;
+using ::mpact::sim::generic::SourceOperandInterface;
+using TestRegister = ::mpact::sim::generic::Register<uint32_t>;
+
+template <typename RegType>
+inline RegType *GetRegister(ArchState *state, std::string name) {
+  auto iter = state->registers()->find(name);
+  if (iter == state->registers()->end()) {
+    return nullptr;
+  }
+  return static_cast<RegType *>(iter->second);
+}
+
+// Generic helper functions to create register operands.
+template <typename RegType>
+inline DestinationOperandInterface *GetRegisterDestinationOp(ArchState *state,
+                                                             std::string name,
+                                                             int latency) {
+  auto *reg = GetRegister<RegType>(state, name);
+  return reg->CreateDestinationOperand(latency);
+}
+
+template <typename RegType>
+inline DestinationOperandInterface *GetRegisterDestinationOp(
+    ArchState *state, std::string name, int latency, std::string op_name) {
+  auto *reg = GetRegister<RegType>(state, name);
+  return reg->CreateDestinationOperand(latency, op_name);
+}
+
+template <typename RegType>
+inline SourceOperandInterface *GetRegisterSourceOp(ArchState *state,
+                                                   std::string name) {
+  auto *reg = GetRegister<RegType>(state, name);
+  auto *op = reg->CreateSourceOperand();
+  return op;
+}
+
+template <typename RegType>
+inline SourceOperandInterface *GetRegisterSourceOp(ArchState *state,
+                                                   std::string name,
+                                                   std::string op_name) {
+  auto *reg = GetRegister<RegType>(state, name);
+  auto *op = reg->CreateSourceOperand(op_name);
+  return op;
+}
+
+PushPopEncoding::PushPopEncoding(ArchState *state) : state_(state) {
+  source_op_getters_.insert(std::make_pair(
+      *SourceOpEnum::kRlist, [this]() -> SourceOperandInterface * {
+        return new generic::ImmediateOperand<uint32_t>(
+            p_type::ExtractRlist(inst_word_));
+      }));
+
+  source_op_getters_.insert(std::make_pair(
+      *SourceOpEnum::kSpimm6, [this]() -> SourceOperandInterface * {
+        return new generic::ImmediateOperand<uint32_t>(
+            p_type::ExtractSpimm6(inst_word_));
+      }));
+
+  source_op_getters_.insert(
+      std::make_pair(*SourceOpEnum::kX2, [this]() -> SourceOperandInterface * {
+        return GetRegisterSourceOp<TestRegister>(state_, "x2");
+      }));
+
+  list_source_op_getters_.insert(std::make_pair(
+      *ListSourceOpEnum::kRlist,
+      [this]() -> std::vector<SourceOperandInterface *> {
+        std::vector<SourceOperandInterface *> result;
+        auto rlist = p_type::ExtractRlist(inst_word_);
+        // Get the value of 'rlist', and add source operands accordingly.
+        if (rlist < 4) return result;
+        result.push_back(GetRegisterSourceOp<TestRegister>(state_, "x1"));
+        if (rlist == 4) return result;
+        result.push_back(GetRegisterSourceOp<TestRegister>(state_, "x8"));
+        if (rlist == 5) return result;
+        result.push_back(GetRegisterSourceOp<TestRegister>(state_, "x9"));
+        if (rlist == 6) return result;
+        result.push_back(GetRegisterSourceOp<TestRegister>(state_, "x18"));
+        if (rlist == 7) return result;
+        result.push_back(GetRegisterSourceOp<TestRegister>(state_, "x19"));
+        if (rlist == 8) return result;
+        result.push_back(GetRegisterSourceOp<TestRegister>(state_, "x20"));
+        if (rlist == 9) return result;
+        result.push_back(GetRegisterSourceOp<TestRegister>(state_, "x21"));
+        if (rlist == 10) return result;
+        result.push_back(GetRegisterSourceOp<TestRegister>(state_, "x22"));
+        if (rlist == 11) return result;
+        result.push_back(GetRegisterSourceOp<TestRegister>(state_, "x23"));
+        if (rlist == 12) return result;
+        result.push_back(GetRegisterSourceOp<TestRegister>(state_, "x24"));
+        if (rlist == 13) return result;
+        result.push_back(GetRegisterSourceOp<TestRegister>(state_, "x25"));
+        if (rlist == 14) return result;
+        result.push_back(GetRegisterSourceOp<TestRegister>(state_, "x26"));
+        result.push_back(GetRegisterSourceOp<TestRegister>(state_, "x26"));
+        return result;
+      }));
+
+  dest_op_getters_.insert(std::make_pair(
+      *DestOpEnum::kX2, [this](int latency) -> DestinationOperandInterface * {
+        return GetRegisterDestinationOp<TestRegister>(state_, "x2", latency);
+      }));
+
+  list_dest_op_getters_.insert(
+      std::make_pair(*ListDestOpEnum::kRlist,
+                     [this](const std::vector<int> &latency)
+                         -> std::vector<DestinationOperandInterface *> {
+                       std::vector<DestinationOperandInterface *> result;
+                       // Get the value of 'rlist', and add destination operands
+                       // accordingly.
+                       auto rlist = p_type::ExtractRlist(inst_word_);
+                       int size = latency.size();
+                       if (rlist < 4) return result;
+                       result.push_back(GetRegisterDestinationOp<TestRegister>(
+                           state_, "x1", latency[result.size() % size]));
+                       if (rlist == 4) return result;
+                       result.push_back(GetRegisterDestinationOp<TestRegister>(
+                           state_, "x8", latency[result.size() % size]));
+                       if (rlist == 5) return result;
+                       result.push_back(GetRegisterDestinationOp<TestRegister>(
+                           state_, "x9", latency[result.size() % size]));
+                       if (rlist == 6) return result;
+                       result.push_back(GetRegisterDestinationOp<TestRegister>(
+                           state_, "x18", latency[result.size() % size]));
+                       if (rlist == 7) return result;
+                       result.push_back(GetRegisterDestinationOp<TestRegister>(
+                           state_, "x19", latency[result.size() % size]));
+                       if (rlist == 8) return result;
+                       result.push_back(GetRegisterDestinationOp<TestRegister>(
+                           state_, "x20", latency[result.size() % size]));
+                       if (rlist == 9) return result;
+                       result.push_back(GetRegisterDestinationOp<TestRegister>(
+                           state_, "x21", latency[result.size() % size]));
+                       if (rlist == 10) return result;
+                       result.push_back(GetRegisterDestinationOp<TestRegister>(
+                           state_, "x22", latency[result.size() % size]));
+                       if (rlist == 11) return result;
+                       result.push_back(GetRegisterDestinationOp<TestRegister>(
+                           state_, "x23", latency[result.size() % size]));
+                       if (rlist == 12) return result;
+                       result.push_back(GetRegisterDestinationOp<TestRegister>(
+                           state_, "x24", latency[result.size() % size]));
+                       if (rlist == 13) return result;
+                       result.push_back(GetRegisterDestinationOp<TestRegister>(
+                           state_, "x25", latency[result.size() % size]));
+                       if (rlist == 14) return result;
+                       result.push_back(GetRegisterDestinationOp<TestRegister>(
+                           state_, "x26", latency[result.size() % size]));
+                       result.push_back(GetRegisterDestinationOp<TestRegister>(
+                           state_, "x26", latency[result.size() % size]));
+                       return result;
+                     }));
+}
+
+void PushPopEncoding::ParseInstruction(uint16_t inst_word) {
+  inst_word_ = inst_word;
+  opcode_ = DecodePushPop(inst_word_);
+}
+
+SourceOperandInterface *PushPopEncoding::GetSource(SlotEnum, int, OpcodeEnum,
+                                                   SourceOpEnum op,
+                                                   int source_no) {
+  auto iter = source_op_getters_.find(*op);
+  if (iter == source_op_getters_.end()) {
+    return nullptr;
+  }
+  return iter->second();
+}
+
+std::vector<SourceOperandInterface *> PushPopEncoding::GetSources(
+    SlotEnum, int, OpcodeEnum, ListSourceOpEnum op, int source_no) {
+  auto iter = list_source_op_getters_.find(*op);
+  if (iter == list_source_op_getters_.end()) {
+    return {};
+  }
+  return iter->second();
+}
+
+DestinationOperandInterface *PushPopEncoding::GetDestination(
+    SlotEnum, int, OpcodeEnum, DestOpEnum op, int dest_no, int latency) {
+  auto iter = dest_op_getters_.find(*op);
+  if (iter == dest_op_getters_.end()) {
+    return nullptr;
+  }
+  return iter->second(latency);
+}
+
+std::vector<DestinationOperandInterface *> PushPopEncoding::GetDestinations(
+    SlotEnum, int, OpcodeEnum, ListDestOpEnum op, int dest_no,
+    const std::vector<int> &latency) {
+  auto iter = list_dest_op_getters_.find(*op);
+  if (iter == list_dest_op_getters_.end()) {
+    return {};
+  }
+  return iter->second(latency);
+}
+
+}  // namespace mpact::sim::decoder::test
diff --git a/mpact/sim/decoder/test/push_pop_encoding.h b/mpact/sim/decoder/test/push_pop_encoding.h
new file mode 100644
index 0000000..4dec95e
--- /dev/null
+++ b/mpact/sim/decoder/test/push_pop_encoding.h
@@ -0,0 +1,84 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef MPACT_SIM_DECODER_TEST_PUSH_POP_ENCODING_H_
+#define MPACT_SIM_DECODER_TEST_PUSH_POP_ENCODING_H_
+
+#include <cstdint>
+#include <vector>
+
+#include "absl/container/flat_hash_map.h"
+#include "absl/functional/any_invocable.h"
+#include "mpact/sim/decoder/test/push_pop_inst_decoder.h"
+#include "mpact/sim/decoder/test/push_pop_inst_enums.h"
+#include "mpact/sim/generic/arch_state.h"
+#include "mpact/sim/generic/operand_interface.h"
+
+// This file defines the encoding class for the push/pop test case isa.
+namespace mpact::sim::decoder::test {
+
+using ::mpact::sim::generic::ArchState;
+using ::mpact::sim::generic::DestinationOperandInterface;
+using ::mpact::sim::generic::SourceOperandInterface;
+
+class PushPopEncoding : public PushPopInstEncodingBase {
+ public:
+  explicit PushPopEncoding(ArchState *state);
+  ~PushPopEncoding() override = default;
+
+  void ParseInstruction(uint16_t inst_word);
+  OpcodeEnum GetOpcode(SlotEnum slot, int entry) override { return opcode_; }
+  OpcodeEnum opcode() const { return opcode_; }
+  // Return a single source operand for the given slot, entry, opcode, and
+  // source operand enum.
+  SourceOperandInterface *GetSource(SlotEnum, int, OpcodeEnum, SourceOpEnum op,
+                                    int source_no) override;
+  // Expand the ListSourceOpEnum into a vector of source operands.
+  std::vector<SourceOperandInterface *> GetSources(SlotEnum, int, OpcodeEnum,
+                                                   ListSourceOpEnum op,
+                                                   int source_no) override;
+  // Return a single destination operand for the given slot, entry, opcode,
+  // destination operand enum, and latency.
+  DestinationOperandInterface *GetDestination(SlotEnum, int, OpcodeEnum,
+                                              DestOpEnum op, int dest_no,
+                                              int latency) override;
+  // Expand the ListDestOpEnum into a vector of destination operands.
+  std::vector<DestinationOperandInterface *> GetDestinations(
+      SlotEnum, int, OpcodeEnum, ListDestOpEnum op, int dest_no,
+      const std::vector<int> &latency) override;
+
+ private:
+  using SourceOpGetterMap =
+      absl::flat_hash_map<int, absl::AnyInvocable<SourceOperandInterface *()>>;
+  using DestOpGetterMap = absl::flat_hash_map<
+      int, absl::AnyInvocable<DestinationOperandInterface *(int)>>;
+  using ListSourceOpGetterMap = absl::flat_hash_map<
+      int, absl::AnyInvocable<std::vector<SourceOperandInterface *>()>>;
+  using ListDestOpGetterMap = absl::flat_hash_map<
+      int, absl::AnyInvocable<std::vector<DestinationOperandInterface *>(
+               const std::vector<int> &)>>;
+
+  ArchState *state_;
+  OpcodeEnum opcode_;
+  uint16_t inst_word_;
+
+  SourceOpGetterMap source_op_getters_;
+  DestOpGetterMap dest_op_getters_;
+  ListSourceOpGetterMap list_source_op_getters_;
+  ListDestOpGetterMap list_dest_op_getters_;
+};
+
+}  // namespace mpact::sim::decoder::test
+
+#endif  // MPACT_SIM_DECODER_TEST_PUSH_POP_ENCODING_H_
diff --git a/mpact/sim/decoder/test/testfiles/example.isa b/mpact/sim/decoder/test/testfiles/example.isa
index 521b2af..3a6cc47 100644
--- a/mpact/sim/decoder/test/testfiles/example.isa
+++ b/mpact/sim/decoder/test/testfiles/example.isa
@@ -42,6 +42,8 @@
     cvtfs{(pred : sy : dest(base + 1))};
     adds{(pred : sy, sx : dest(base))},
       attributes: {five = 1, six};
+    push{(pred : [rlist]: )};
+    pop{(pred : : [rlist])};
     addf{(pred : sy, sx : dest(base))};
     subf{(pred : sy, sx : dest(base))};
     mulf{(pred : sy, sx : dest(mult_plus_2))};
diff --git a/mpact/sim/generic/BUILD b/mpact/sim/generic/BUILD
index 11730ac..d38e144 100644
--- a/mpact/sim/generic/BUILD
+++ b/mpact/sim/generic/BUILD
@@ -96,6 +96,7 @@
         ":program_error",
         "@com_google_absl//absl/base:core_headers",
         "@com_google_absl//absl/container:flat_hash_map",
+        "@com_google_absl//absl/container:flat_hash_set",
         "@com_google_absl//absl/functional:any_invocable",
         "@com_google_absl//absl/status",
         "@com_google_absl//absl/strings",
diff --git a/mpact/sim/generic/arch_state.cc b/mpact/sim/generic/arch_state.cc
index c38c62a..4a82638 100644
--- a/mpact/sim/generic/arch_state.cc
+++ b/mpact/sim/generic/arch_state.cc
@@ -17,8 +17,15 @@
 #include <string>
 #include <utility>
 
+#include "absl/container/flat_hash_set.h"
+#include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
+#include "mpact/sim/generic/component.h"
+#include "mpact/sim/generic/data_buffer.h"
 #include "mpact/sim/generic/fifo.h"
+#include "mpact/sim/generic/function_delay_line.h"
+#include "mpact/sim/generic/operand_interface.h"
+#include "mpact/sim/generic/program_error.h"
 #include "mpact/sim/generic/register.h"
 
 namespace mpact {