Initial end-to-end simulation support

Change-Id: Ifcdfb8e0ee3e40b9071ade2ff5dcab5037ec7887
Signed-off-by: Zane Shelley <zshelle@us.ibm.com>
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