Initial end-to-end simulation support

Change-Id: Ifcdfb8e0ee3e40b9071ade2ff5dcab5037ec7887
Signed-off-by: Zane Shelley <zshelle@us.ibm.com>
diff --git a/src/hei_chip.hpp b/src/hei_chip.hpp
index fbb7e6c..3b17164 100644
--- a/src/hei_chip.hpp
+++ b/src/hei_chip.hpp
@@ -25,10 +25,12 @@
      * @param i_chip See description for iv_chip.
      * @param i_type See description for iv_type.
      */
-    Chip(void* i_chip, ChipType_t i_type) : iv_chip(i_chip), iv_type(i_type) {}
+    Chip(const void* i_chip, ChipType_t i_type) :
+        iv_chip(i_chip), iv_type(i_type)
+    {}
 
   public: // Accessors
-    void* getChip() const
+    const void* getChip() const
     {
         return iv_chip;
     }
@@ -58,7 +60,7 @@
      * purpose is to eventually get passed back to the user application with
      * information associated with each chip.
      */
-    void* iv_chip = nullptr;
+    const void* iv_chip = nullptr;
 
     /**
      * When doing analysis on a chip, the isolator will need to know the chip
diff --git a/src/hei_main.hpp b/src/hei_main.hpp
index e812bef..21f2f66 100644
--- a/src/hei_main.hpp
+++ b/src/hei_main.hpp
@@ -57,7 +57,7 @@
  *                     this function is called and a chip type has already been
  *                     initialized:
  *                      - false (default), the function will return
- *                        RC_CDF_INITIALIZED and exit.
+ *                        RC_CHIP_DATA_INITIALIZED and exit.
  *                      - true, the function will delete the previous isolation
  *                        objects for this chip type and reinitialize.
  *
diff --git a/src/hei_user_interface.hpp b/src/hei_user_interface.hpp
index 6fb887c..9849ca1 100644
--- a/src/hei_user_interface.hpp
+++ b/src/hei_user_interface.hpp
@@ -16,12 +16,9 @@
 /**
  * @brief Performs a hardware register read operation.
  *
- * @param i_chip     This is a pointer to a user application object that
- *                   represents the target chip. It is provided to the isolator
- *                   by the user application via the isolator main APIs. The
- *                   isolator does not know anything about this object nor how
- *                   to use it. The user application is responsible for knowing
- *                   what to do with this parameter.
+ * @param i_chip     The target chip for the register access. It is provided to
+ *                   the isolator by the user application via the isolator main
+ *                   APIs.
  *
  * @param o_buffer   Allocated memory space for the returned contents of the
  *                   register.
@@ -44,7 +41,7 @@
  *         failure the user application is responsible for reporting why the
  *         register access failed.
  */
-ReturnCode registerRead(void* i_chip, void* o_buffer, size_t& io_bufSize,
+ReturnCode registerRead(const Chip& i_chip, void* o_buffer, size_t& io_bufSize,
                         uint64_t i_regType, uint64_t i_address);
 
 #ifndef __HEI_READ_ONLY
@@ -52,12 +49,9 @@
 /**
  * @brief Performs a hardware register write operation.
  *
- * @param i_chip     This is a pointer to a user application object that
- *                   represents the target chip. It is provided to the isolator
- *                   by the user application via the isolator main APIs. The
- *                   isolator does not know anything about this object nor how
- *                   to use it. The user application is responsible for knowing
- *                   what to do with this parameter.
+ * @param i_chip     The target chip for the register access. It is provided to
+ *                   the isolator by the user application via the isolator main
+ *                   APIs.
  *
  * @param i_buffer   Allocated memory space containing the register contents to
  *                   write to hardware.
@@ -80,7 +74,7 @@
  *         failure the user application is responsible for reporting why the
  *         register access failed.
  */
-ReturnCode registerWrite(void* i_chip, void* i_buffer, size_t& io_bufSize,
+ReturnCode registerWrite(const Chip& i_chip, void* i_buffer, size_t& io_bufSize,
                          uint64_t i_regType, uint64_t i_address);
 
 #endif
diff --git a/src/isolator/hei_isolation_node.cpp b/src/isolator/hei_isolation_node.cpp
index 8d67094..e2bf61a 100644
--- a/src/isolator/hei_isolation_node.cpp
+++ b/src/isolator/hei_isolation_node.cpp
@@ -21,7 +21,7 @@
     const BitString* bs = rule_itr->second->getBitString(i_chip);
 
     // Ensure this BitString is not longer than the maximum bit field.
-    HEI_ASSERT(bs->getBitLen() <= sizeof(RegisterBit_t) * 8);
+    HEI_ASSERT(bs->getBitLen() <= (1 << (sizeof(RegisterBit_t) * 8)));
 
     // Find all active bits for this rule.
     for (RegisterBit_t bit = 0; bit < bs->getBitLen(); bit++)
diff --git a/src/isolator/hei_isolator.cpp b/src/isolator/hei_isolator.cpp
index 759eb13..68dda7d 100644
--- a/src/isolator/hei_isolator.cpp
+++ b/src/isolator/hei_isolator.cpp
@@ -5,6 +5,7 @@
 #include <util/hei_flyweight.hpp>
 
 // BEGIN temporary code
+#include <isolator/hei_isolation_node.hpp>
 #include <register/hei_scom_register.hpp>
 // END temporary code
 
@@ -20,18 +21,28 @@
     HEI_INF("Isolator::initialize(%p,%lu,%d)", i_buffer, i_bufferSize,
             i_forceInit);
 
-    Flyweight<ScomRegister>& sfw   = Flyweight<ScomRegister>::getSingleton();
-    Flyweight<IdScomRegister>& ifw = Flyweight<IdScomRegister>::getSingleton();
+    auto& scom_fw    = Flyweight<ScomRegister>::getSingleton();
+    auto& idScom_fw  = Flyweight<IdScomRegister>::getSingleton();
+    auto& isoNode_fw = Flyweight<IsolationNode>::getSingleton();
 
-    sfw.get(ScomRegister{CHIP_TYPE_INVALID, REG_ID_INVALID, REG_INST_DEFAULT,
-                         REG_ACCESS_RW, 0x01234567});
-    sfw.get(ScomRegister{CHIP_TYPE_INVALID, REG_ID_INVALID, REG_INST_DEFAULT,
-                         REG_ACCESS_RW, 0x00112233});
+    auto& idScom0 = idScom_fw.get(IdScomRegister{
+        static_cast<ChipType_t>(0xdeadbeef), static_cast<RegisterId_t>(0x1111),
+        REG_INST_DEFAULT, REG_ACCESS_RW, 0x80000000FF000000});
 
-    ifw.get(IdScomRegister{CHIP_TYPE_INVALID, REG_ID_INVALID, REG_INST_DEFAULT,
-                           REG_ACCESS_RW, 0x0123456789abcdef});
-    ifw.get(IdScomRegister{CHIP_TYPE_INVALID, REG_ID_INVALID, REG_INST_DEFAULT,
-                           REG_ACCESS_RW, 0x0011223344556677});
+    auto& scom0 = scom_fw.get(ScomRegister{
+        static_cast<ChipType_t>(0xdeadbeef), static_cast<RegisterId_t>(0x2222),
+        REG_INST_DEFAULT, REG_ACCESS_RW, 0x00FF0000});
+
+    auto& node0 = isoNode_fw.get(IsolationNode{idScom0});
+    auto& node1 = isoNode_fw.get(IsolationNode{scom0});
+
+    node0.addRule(ATTN_TYPE_CHECKSTOP, &idScom0);
+    node0.addChild(48, &node1);
+
+    node1.addRule(ATTN_TYPE_CHECKSTOP, &scom0);
+
+    iv_isoStart[static_cast<ChipType_t>(0xdeadbeef)].push_back(
+        {&node0, ATTN_TYPE_CHECKSTOP});
     // END temporary code
 
     return rc;
@@ -68,6 +79,23 @@
     {
         // BEGIN temporary code
         HEI_INF("Isolator::isolate(%p,%u)", chip.getChip(), chip.getType());
+
+        // Isolation objects for this chip's type must exist.
+        const auto& chip_itr = iv_isoStart.find(chip.getType());
+        HEI_ASSERT(iv_isoStart.end() != chip_itr);
+
+        for (const auto& pair : chip_itr->second)
+        {
+            if (pair.first->analyze(chip, pair.second, o_isoData))
+            {
+                for (const auto& sig : o_isoData.getSignatureList())
+                {
+                    HEI_INF("Signature(%p,0x%x,0x%x,0x%x,0x%x)",
+                            sig.getChip().getChip(), sig.getId(),
+                            sig.getInstance(), sig.getBit(), sig.getAttnType());
+                }
+            }
+        }
         // END temporary code
     }
 
diff --git a/src/isolator/hei_isolator.hpp b/src/isolator/hei_isolator.hpp
index d779b7b..c60835d 100644
--- a/src/isolator/hei_isolator.hpp
+++ b/src/isolator/hei_isolator.hpp
@@ -2,6 +2,7 @@
 
 #include <hei_includes.hpp>
 #include <hei_isolation_data.hpp>
+#include <isolator/hei_isolation_node.hpp>
 
 namespace libhei
 {
@@ -68,6 +69,18 @@
     /** @brief See API wrapper description in hei_main.hpp. */
     ReturnCode isolate(const std::vector<Chip>& i_chipList,
                        IsolationData& o_isoData) const;
+
+  private:
+    // BEGIN temporary code
+    /**
+     * Provides a list of isolation tree nodes used to start analysis based on
+     * the chip type and attention type.
+     */
+    std::map<ChipType_t,
+             std::vector<std::pair<const IsolationNode*, AttentionType_t>>>
+        iv_isoStart;
+    // END temporary code
+
 }; // end class Isolator
 
 } // end namespace libhei
diff --git a/src/isolator/hei_signature.hpp b/src/isolator/hei_signature.hpp
index 844aad6..2bf91e1 100644
--- a/src/isolator/hei_signature.hpp
+++ b/src/isolator/hei_signature.hpp
@@ -78,6 +78,51 @@
     {
         return iv_attnType;
     }
+
+  public: // Operators
+    /** @brief Equals operator. */
+    bool operator==(const Signature& i_r) const
+    {
+        return (getChip() == i_r.getChip() && getId() == i_r.getId() &&
+                getInstance() == i_r.getInstance() &&
+                getBit() == i_r.getBit() && getAttnType() == i_r.getAttnType());
+    }
+
+    /** @brief Less than operator. */
+    bool operator<(const Signature& i_r) const
+    {
+        if (getChip() < i_r.getChip())
+        {
+            return true;
+        }
+        else if (getChip() == i_r.getChip())
+        {
+            if (getId() < i_r.getId())
+            {
+                return true;
+            }
+            else if (getId() == i_r.getId())
+            {
+                if (getInstance() < i_r.getInstance())
+                {
+                    return true;
+                }
+                else if (getInstance() == i_r.getInstance())
+                {
+                    if (getBit() < i_r.getBit())
+                    {
+                        return true;
+                    }
+                    else if (getBit() == i_r.getBit())
+                    {
+                        return (getAttnType() < i_r.getAttnType());
+                    }
+                }
+            }
+        }
+
+        return false;
+    }
 };
 
 } // end namespace libhei
diff --git a/src/register/hei_hardware_register.cpp b/src/register/hei_hardware_register.cpp
index 7df5a3e..7629a06 100644
--- a/src/register/hei_hardware_register.cpp
+++ b/src/register/hei_hardware_register.cpp
@@ -88,8 +88,8 @@
         size_t sz_buffer = BitString::getMinBytes(bs.getBitLen());
 
         // Read this register from hardware.
-        rc = registerRead(i_chip.getChip(), bs.getBufAddr(), sz_buffer,
-                          getRegisterType(), getAddress());
+        rc = registerRead(i_chip, bs.getBufAddr(), sz_buffer, getRegisterType(),
+                          getAddress());
         if (RC_SUCCESS != rc)
         {
             // The read failed and we can't trust what was put in the register
@@ -132,8 +132,8 @@
     size_t sz_buffer = BitString::getMinBytes(bs.getBitLen());
 
     // Write to this register to hardware.
-    rc = registerWrite(i_chip.getChip(), bs.getBufAddr(), sz_buffer,
-                       getRegisterType(), getAddress());
+    rc = registerWrite(i_chip, bs.getBufAddr(), sz_buffer, getRegisterType(),
+                       getAddress());
 
     if (RC_SUCCESS == rc)
     {
diff --git a/test/simulator/hei_sim_main.cpp b/test/simulator/hei_sim_main.cpp
deleted file mode 100644
index c2bb24e..0000000
--- a/test/simulator/hei_sim_main.cpp
+++ /dev/null
@@ -1,25 +0,0 @@
-#include <hei_main.hpp>
-
-int main()
-{
-    using namespace libhei;
-
-    void* buffer     = nullptr;
-    size_t sz_buffer = 0;
-
-    initialize(buffer, sz_buffer);
-
-    Chip c1, c2;
-
-    std::vector<Chip> chipList;
-    chipList.push_back(c1);
-    chipList.push_back(c2);
-
-    IsolationData isoData;
-
-    isolate(chipList, isoData);
-
-    uninitialize();
-
-    return 0;
-}
diff --git a/test/simulator/hei_sim_user_interface.cpp b/test/simulator/hei_sim_user_interface.cpp
deleted file mode 100644
index f1025c9..0000000
--- a/test/simulator/hei_sim_user_interface.cpp
+++ /dev/null
@@ -1,57 +0,0 @@
-#include <hei_includes.hpp>
-#include <hei_user_interface.hpp>
-
-namespace libhei
-{
-
-//------------------------------------------------------------------------------
-
-ReturnCode registerRead(void* i_chip, void* o_buffer, size_t& io_bufSize,
-                        uint64_t i_regType, uint64_t i_address)
-{
-    ReturnCode rc;
-
-    HEI_ASSERT(nullptr != i_chip);
-    HEI_ASSERT(nullptr != o_buffer);
-    HEI_ASSERT(0 != io_bufSize);
-
-    switch (i_regType)
-    {
-        default:
-            rc = RC_REG_ACCESS_FAILURE;
-            HEI_ERR("registerRead(%p,%p,%lx,%lx,%lx)", i_chip, o_buffer,
-                    io_bufSize, i_regType, i_address);
-    }
-
-    return rc;
-}
-
-//------------------------------------------------------------------------------
-
-#ifndef __HEI_READ_ONLY
-
-ReturnCode registerWrite(void* i_chip, void* i_buffer, size_t& io_bufSize,
-                         uint64_t i_regType, uint64_t i_address)
-{
-    ReturnCode rc;
-
-    HEI_ASSERT(nullptr != i_chip);
-    HEI_ASSERT(nullptr != i_buffer);
-    HEI_ASSERT(0 != io_bufSize);
-
-    switch (i_regType)
-    {
-        default:
-            rc = RC_REG_ACCESS_FAILURE;
-            HEI_ERR("registerWrite(%p,%p,%lx,%lx,%lx)", i_chip, i_buffer,
-                    io_bufSize, i_regType, i_address);
-    }
-
-    return rc;
-}
-
-#endif
-
-//------------------------------------------------------------------------------
-
-} // end namespace libhei
diff --git a/test/simulator/meson.build b/test/simulator/meson.build
index 5b8a228..88de4b8 100644
--- a/test/simulator/meson.build
+++ b/test/simulator/meson.build
@@ -1,14 +1,27 @@
-sim_sources = [
-    'hei_sim_main.cpp',
-    'hei_sim_user_interface.cpp',
+# Simulator sources
+sim_src = [
+    'simulator.cpp',
+    'user_interface.cpp',
+]
+
+# Isolator sources
+iso_src = [
     '../../src/isolator/hei_isolator.cpp',
     '../../src/isolator/hei_isolation_node.cpp',
     '../../src/register/hei_hardware_register.cpp',
     '../../src/util/hei_bit_string.cpp',
 ]
 
-sim = executable('simulator', sim_sources, \
-                    include_directories: incdir)
+# Test cases
+test_src = [
+    'sample_test_case.cpp',
+]
 
-test('simulator', sim)
+gtest = dependency('gtest', main : true, required : false, method : 'system')
+
+if gtest.found()
+    test('simulator', \
+         executable('simulator', sim_src, iso_src, test_src, \
+                    dependencies : gtest, include_directories: incdir))
+endif
 
diff --git a/test/simulator/sample_test_case.cpp b/test/simulator/sample_test_case.cpp
new file mode 100644
index 0000000..1a44a32
--- /dev/null
+++ b/test/simulator/sample_test_case.cpp
@@ -0,0 +1,18 @@
+#include "simulator.hpp"
+
+START_TEST_CASE(SampleTestSet1)
+
+CHIP(proc0, 0xdeadbeef)
+
+START_ITERATION
+
+REG_IDSCOM(proc0, 0x80000000FF000000, 0x8000) // parent FIR bit 48
+
+REG_SCOM(proc0, 0x00FF0000, 0x8800000000000000) // child FIR bits 0 and 4
+
+EXP_SIG(proc0, 0x2222, 0, 0, CHECKSTOP)
+EXP_SIG(proc0, 0x2222, 0, 4, CHECKSTOP)
+
+END_ITERATION
+
+END_TEST_CASE
diff --git a/test/simulator/simulator.cpp b/test/simulator/simulator.cpp
new file mode 100644
index 0000000..0c307c2
--- /dev/null
+++ b/test/simulator/simulator.cpp
@@ -0,0 +1,54 @@
+#include "simulator.hpp"
+
+using namespace libhei;
+
+//------------------------------------------------------------------------------
+
+void SimulatorData::addChip(const Chip& i_chip)
+{
+    // First check if this entry already exists.
+    auto itr = std::find(iv_chipList.begin(), iv_chipList.end(), i_chip);
+    ASSERT_EQ(iv_chipList.end(), itr);
+
+    // Add the new entry.
+    iv_chipList.push_back(i_chip);
+
+    // TODO: Find the chip data file based on the chip type from i_chip. Read
+    //       that file in to memory and call initialize.
+    void* buffer     = nullptr;
+    size_t sz_buffer = 0;
+
+    ReturnCode rc = initialize(buffer, sz_buffer);
+    ASSERT_TRUE(RC_SUCCESS == rc || RC_CHIP_DATA_INITIALIZED == rc);
+}
+
+//------------------------------------------------------------------------------
+
+void SimulatorData::endIteration()
+{
+    // Start by calling libhei::isolate().
+    IsolationData isoData{};
+    ReturnCode rc = isolate(iv_chipList, isoData);
+
+    // It is possible that even in a failure scenario the information in the
+    // returned IsolationData would be useful.
+    // TODO: Figure out where to put the data.
+
+    // Verify if isolation completed successfully.
+    ASSERT_TRUE(RC_SUCCESS == rc);
+
+    // Get the list of signatures found in isolation.
+    std::vector<Signature> givenSigList = isoData.getSignatureList();
+
+    // Verify the expected list and given list are the same.
+    ASSERT_EQ(iv_expSigList.size(), givenSigList.size());
+
+    std::sort(iv_expSigList.begin(), iv_expSigList.end());
+    std::sort(givenSigList.begin(), givenSigList.end());
+
+    ASSERT_TRUE(std::equal(givenSigList.begin(), givenSigList.end(),
+                           iv_expSigList.begin()));
+
+    // The iteration is complete so we can flush the data.
+    flushIterationData();
+}
diff --git a/test/simulator/simulator.hpp b/test/simulator/simulator.hpp
new file mode 100644
index 0000000..9fe5c2b
--- /dev/null
+++ b/test/simulator/simulator.hpp
@@ -0,0 +1,230 @@
+#pragma once
+
+#include <hei_main.hpp>
+
+#include "gtest/gtest.h"
+
+namespace libhei
+{
+
+/**
+ * @brief Contains simulated chip objects and register contents used during
+ *        isolation. Also contains the expected signatures to compare after
+ *        isolation.
+ */
+class SimulatorData
+{
+  private: // This class cannot be instantiated. Use getSingleton() instead.
+    /** @brief Default constructor. */
+    SimulatorData() = default;
+
+    /** @brief Destructor. */
+    ~SimulatorData() = default;
+
+    /** @brief Copy constructor. */
+    SimulatorData(const SimulatorData&) = delete;
+
+    /** @brief Assignment operator. */
+    SimulatorData& operator=(const SimulatorData&) = delete;
+
+  public:
+    /** @brief Provides access to a singleton instance of this object. */
+    static SimulatorData& getSingleton()
+    {
+        static SimulatorData theSimData{};
+        return theSimData;
+    }
+
+  private:
+    /** The list of configured chips used throughout a test case. */
+    std::vector<Chip> iv_chipList;
+
+    /** The contents of all the SCOM registers used for an iteration of
+     *  isolation. */
+    std::map<Chip, std::map<uint32_t, uint64_t>> iv_scomRegData;
+
+    /** The contents of all the Indirect SCOM registers used for an iteration of
+     *  isolation. */
+    std::map<Chip, std::map<uint64_t, uint64_t>> iv_idScomRegData;
+
+    /** The list of expected signatures during an iteration of isolation. */
+    std::vector<Signature> iv_expSigList;
+
+  public:
+    /**
+     * @brief Adds a chip to the list of configured chips. Also, calls the main
+     *        initialize() API which will initialize the isolator with the Chip
+     *        Data File associated with this chip.
+     */
+    void addChip(const Chip& i_chip);
+
+    /** @brief Adds a SCOM register to iv_scomRegData. */
+    void addScomReg(const Chip& i_chip, uint32_t i_address, uint64_t i_value)
+    {
+        // First check if this entry already exists.
+        auto chip_itr = iv_scomRegData.find(i_chip);
+        if (iv_scomRegData.end() != chip_itr)
+        {
+            auto addr_itr = chip_itr->second.find(i_address);
+            ASSERT_EQ(chip_itr->second.end(), addr_itr);
+        }
+
+        // Add the new entry.
+        iv_scomRegData[i_chip][i_address] = i_value;
+    }
+
+    /** @brief Adds a SCOM register to iv_idScomRegData. */
+    void addIdScomReg(const Chip& i_chip, uint64_t i_address, uint64_t i_value)
+    {
+        // First check if this entry already exists.
+        auto chip_itr = iv_idScomRegData.find(i_chip);
+        if (iv_idScomRegData.end() != chip_itr)
+        {
+            auto addr_itr = chip_itr->second.find(i_address);
+            ASSERT_EQ(chip_itr->second.end(), addr_itr);
+        }
+
+        // Add the new entry.
+        iv_idScomRegData[i_chip][i_address] = i_value;
+    }
+
+    /** @brief Adds a Signature to iv_expSigList. */
+    void addSignature(const Signature& i_signature)
+    {
+        // First check if this entry already exists.
+        auto itr =
+            std::find(iv_expSigList.begin(), iv_expSigList.end(), i_signature);
+        ASSERT_EQ(iv_expSigList.end(), itr);
+
+        // Add the new entry.
+        iv_expSigList.push_back(i_signature);
+    }
+
+    /**
+     * @brief Flushes register and expected signature lists used for a single
+     *        isolation.
+     */
+    void flushIterationData()
+    {
+        iv_scomRegData.clear();
+        iv_idScomRegData.clear();
+        iv_expSigList.clear();
+    }
+
+    /** @brief Flushes all simulation data. */
+    void flushAll()
+    {
+        flushIterationData();
+        iv_chipList.clear();
+    }
+
+    /**
+     * @brief After an iteration is set up with registers and expected
+     *        signatures, this is called to run the simulation and verify the
+     *        expected signatures.
+     */
+    void endIteration();
+};
+
+} // end namespace libhei
+
+//------------------------------------------------------------------------------
+
+// clang-format off
+
+// The following macros can be used to simplify commonly used function for
+// simulation test cases. At the core of each test case is a Google Test (i.e.
+// gtest), which will do most of the error checking. Just like in gtest, a test
+// case file can contain more than one test. Also, remember that this is all C++
+// code. While it not likely to be used much, you can combine these macros with
+// C++ code to do more advanced test cases. For example, you can put the
+// iteration macros in a loop to walk through each bit of a register.
+
+/**
+ * This is the beginning of a test case. The NAME parameter must be valid C++
+ * identifier and must not contain any underscores (per gtest requirement). To
+ * end the test case use END_TEST_CASE. All contents of the test case must be
+ * contain in between these two macros.
+ */
+#define START_TEST_CASE(NAME)                                                  \
+    TEST(Simulator, NAME)                                                      \
+    {                                                                          \
+        libhei::SimulatorData& simData =                                       \
+            libhei::SimulatorData::getSingleton();                             \
+        simData.flushAll();
+
+/**
+ * Use this to configure a chip object for the test case. There should be an
+ * instance of this macro for each chip required for the test case. Note that
+ * this will also call libhei::initialize() for each new chip type. The CHIP
+ * parameter must be valid C++ identifier because it will be used as the name of
+ * the chip variable. This same identifier will be re-used in several other
+ * macros.
+ */
+#define CHIP(CHIP, TYPE)                                                       \
+    libhei::Chip CHIP{#CHIP, static_cast<libhei::ChipType_t>(TYPE)};           \
+    simData.addChip(CHIP);
+
+/**
+ * Once all of the chips have been configured, there can be one or more
+ * iterations defined in the test case. Use END_ITERATION to end the iteration.
+ * Note that register and signature information will be reset for each
+ * iteration, however, the same set of configure chips will be used for all
+ * iterations within the test case.
+ */
+#define START_ITERATION                                                        \
+    {                                                                          \
+        simData.flushIterationData();
+
+/** This will add a SCOM register to the current iteration. */
+#define REG_SCOM(CHIP, ADDR, VAL)                                              \
+    simData.addScomReg(CHIP, static_cast<uint32_t>(ADDR),                      \
+                       static_cast<uint64_t>(VAL));
+
+/** This will add an Indirect SCOM register to the current iteration. */
+#define REG_IDSCOM(CHIP, ADDR, VAL)                                            \
+    simData.addIdScomReg(CHIP, static_cast<uint64_t>(ADDR),                    \
+                         static_cast<uint64_t>(VAL));
+
+/** This will add an expected signature to the current iteration. */
+#define EXP_SIG(CHIP, ID, INST, BIT, TYPE)                                     \
+    simData.addSignature(libhei::Signature{                                    \
+        CHIP, static_cast<libhei::RegisterId_t>(ID),                           \
+        static_cast<libhei::RegisterInstance_t>(INST),                         \
+        static_cast<libhei::RegisterBit_t>(BIT), libhei::ATTN_TYPE_##TYPE});
+
+/**
+ * This is the end of an iteration that began with START_ITERATION. All of the
+ * register contents and expected signatures will have been stored in the
+ * simulation data. So, this will call libhei::isolate() with the list of
+ * configured chips. Using the register contents in the simulation data,
+ * libhei::isolate() will return a list of signatures (active attentions). That
+ * list will be compared against the expected list of signatures stored in the
+ * simulation data for test case verification.
+ *
+ * You will see that there are two gtest checks for failures:
+ *  - The first check will look to see if any of the previous functions to add
+ *    chips, registers, or signatures to the simulation data failed.
+ *  - The second check will determine if isolation completed successfully and if
+ *    all expected signatures have been verified.
+ * If either check fails, the test case will be aborted regardless if there are
+ * additional iterations in that test case. Note that failure in a test case
+ * will not have any impact on subsequent test cases. Therefore, all test cases
+ * in a file will at least be attempted even if there is a failure.
+ */
+#define END_ITERATION                                                          \
+        if (HasFailure()) { simData.flushAll(); return; }                      \
+        simData.endIteration();                                                \
+        if (HasFailure()) { simData.flushAll(); return; }                      \
+    }
+
+/**
+ * This is the end of the test case that started with START_TEST_CASE. It will
+ * call libhei::uninitialize() and clean up the simulation data.
+ */
+#define END_TEST_CASE                                                          \
+        libhei::uninitialize();                                                \
+        simData.flushAll();                                                    \
+    }
+
+// clang-format on
diff --git a/test/simulator/user_interface.cpp b/test/simulator/user_interface.cpp
new file mode 100644
index 0000000..7ed998a
--- /dev/null
+++ b/test/simulator/user_interface.cpp
@@ -0,0 +1,79 @@
+/**
+ * @file These are the simulated implementations of the user interfaces declared
+ *       in hei_user_interface.hpp
+ */
+
+#include "simulator.hpp"
+
+#include <endian.h>
+
+#include <hei_user_interface.hpp>
+
+namespace libhei
+{
+
+//------------------------------------------------------------------------------
+
+ReturnCode registerRead(const Chip& i_chip, void* o_buffer, size_t& io_bufSize,
+                        uint64_t i_regType, uint64_t i_address)
+{
+    ReturnCode rc{};
+
+    HEI_ASSERT(nullptr != o_buffer);
+    HEI_ASSERT(0 != io_bufSize);
+
+    switch (i_regType)
+    {
+        // BEGIN temporary code
+        // TODO: add cases for REG_TYPE_SCOM and REG_TYPE_ID_SCOM
+        case REG_TYPE_SCOM:
+        {
+            uint64_t x = htobe64(0x8800000000000000);
+            memcpy(o_buffer, &x, sizeof(x));
+            break;
+        }
+        case REG_TYPE_ID_SCOM:
+        {
+            uint64_t x = htobe64(0x8000);
+            memcpy(o_buffer, &x, sizeof(x));
+            break;
+        }
+        // END temporary code
+        default:
+            rc = RC_REG_ACCESS_FAILURE;
+            HEI_ERR("registerRead(%p,%p,%lx,%lx,%lx)", i_chip.getChip(),
+                    o_buffer, io_bufSize, i_regType, i_address);
+    }
+
+    return rc;
+}
+
+//------------------------------------------------------------------------------
+
+#ifndef __HEI_READ_ONLY
+
+ReturnCode registerWrite(const Chip& i_chip, void* i_buffer, size_t& io_bufSize,
+                         uint64_t i_regType, uint64_t i_address)
+{
+    ReturnCode rc{};
+
+    HEI_ASSERT(nullptr != i_buffer);
+    HEI_ASSERT(0 != io_bufSize);
+
+    switch (i_regType)
+    {
+        // TODO: add cases for REG_TYPE_SCOM and REG_TYPE_ID_SCOM
+        default:
+            rc = RC_REG_ACCESS_FAILURE;
+            HEI_ERR("registerWrite(%p,%p,%lx,%lx,%lx)", i_chip.getChip(),
+                    i_buffer, io_bufSize, i_regType, i_address);
+    }
+
+    return rc;
+}
+
+#endif
+
+//------------------------------------------------------------------------------
+
+} // namespace libhei