Zane Shelley | 11b8994 | 2019-11-07 11:07:28 -0600 | [diff] [blame] | 1 | #pragma once |
| 2 | |
| 3 | #include <hei_main.hpp> |
| 4 | |
| 5 | #include "gtest/gtest.h" |
| 6 | |
| 7 | namespace libhei |
| 8 | { |
| 9 | |
| 10 | /** |
| 11 | * @brief Contains simulated chip objects and register contents used during |
| 12 | * isolation. Also contains the expected signatures to compare after |
| 13 | * isolation. |
| 14 | */ |
| 15 | class SimulatorData |
| 16 | { |
| 17 | private: // This class cannot be instantiated. Use getSingleton() instead. |
| 18 | /** @brief Default constructor. */ |
| 19 | SimulatorData() = default; |
| 20 | |
| 21 | /** @brief Destructor. */ |
| 22 | ~SimulatorData() = default; |
| 23 | |
| 24 | /** @brief Copy constructor. */ |
| 25 | SimulatorData(const SimulatorData&) = delete; |
| 26 | |
| 27 | /** @brief Assignment operator. */ |
| 28 | SimulatorData& operator=(const SimulatorData&) = delete; |
| 29 | |
| 30 | public: |
| 31 | /** @brief Provides access to a singleton instance of this object. */ |
| 32 | static SimulatorData& getSingleton() |
| 33 | { |
| 34 | static SimulatorData theSimData{}; |
| 35 | return theSimData; |
| 36 | } |
| 37 | |
Zane Shelley | 1be4c3c | 2020-04-17 15:55:07 -0500 | [diff] [blame^] | 38 | public: |
| 39 | /** The list of supported chip types for the simulator. */ |
| 40 | enum ChipType |
| 41 | { |
| 42 | SAMPLE = 0xdeadbeef, |
| 43 | }; |
| 44 | |
Zane Shelley | 11b8994 | 2019-11-07 11:07:28 -0600 | [diff] [blame] | 45 | private: |
Zane Shelley | 1be4c3c | 2020-04-17 15:55:07 -0500 | [diff] [blame^] | 46 | /** The Chip Data file paths for each support chip type. */ |
| 47 | static const std::map<ChipType, const char*> cv_chipPath; |
| 48 | |
Zane Shelley | 11b8994 | 2019-11-07 11:07:28 -0600 | [diff] [blame] | 49 | /** The list of configured chips used throughout a test case. */ |
| 50 | std::vector<Chip> iv_chipList; |
| 51 | |
| 52 | /** The contents of all the SCOM registers used for an iteration of |
| 53 | * isolation. */ |
| 54 | std::map<Chip, std::map<uint32_t, uint64_t>> iv_scomRegData; |
| 55 | |
| 56 | /** The contents of all the Indirect SCOM registers used for an iteration of |
| 57 | * isolation. */ |
| 58 | std::map<Chip, std::map<uint64_t, uint64_t>> iv_idScomRegData; |
| 59 | |
| 60 | /** The list of expected signatures during an iteration of isolation. */ |
| 61 | std::vector<Signature> iv_expSigList; |
| 62 | |
| 63 | public: |
| 64 | /** |
| 65 | * @brief Adds a chip to the list of configured chips. Also, calls the main |
| 66 | * initialize() API which will initialize the isolator with the Chip |
| 67 | * Data File associated with this chip. |
| 68 | */ |
| 69 | void addChip(const Chip& i_chip); |
| 70 | |
Paul Greenwood | c091934 | 2019-12-10 15:36:17 -0600 | [diff] [blame] | 71 | /** @brief Retrieve ScomReg from map and return its value */ |
| 72 | uint64_t getScomReg(const Chip& i_chip, uint32_t i_address) |
| 73 | { |
| 74 | return iv_scomRegData[i_chip][i_address]; |
| 75 | } |
| 76 | |
| 77 | /** @breif Retrieve idScomReg from map and return its value */ |
| 78 | uint64_t getIdScomReg(const Chip& i_chip, uint64_t i_address) |
| 79 | { |
| 80 | return iv_idScomRegData[i_chip][i_address]; |
| 81 | } |
| 82 | |
Zane Shelley | 11b8994 | 2019-11-07 11:07:28 -0600 | [diff] [blame] | 83 | /** @brief Adds a SCOM register to iv_scomRegData. */ |
| 84 | void addScomReg(const Chip& i_chip, uint32_t i_address, uint64_t i_value) |
| 85 | { |
| 86 | // First check if this entry already exists. |
| 87 | auto chip_itr = iv_scomRegData.find(i_chip); |
| 88 | if (iv_scomRegData.end() != chip_itr) |
| 89 | { |
| 90 | auto addr_itr = chip_itr->second.find(i_address); |
| 91 | ASSERT_EQ(chip_itr->second.end(), addr_itr); |
| 92 | } |
| 93 | |
| 94 | // Add the new entry. |
| 95 | iv_scomRegData[i_chip][i_address] = i_value; |
| 96 | } |
| 97 | |
| 98 | /** @brief Adds a SCOM register to iv_idScomRegData. */ |
| 99 | void addIdScomReg(const Chip& i_chip, uint64_t i_address, uint64_t i_value) |
| 100 | { |
| 101 | // First check if this entry already exists. |
| 102 | auto chip_itr = iv_idScomRegData.find(i_chip); |
| 103 | if (iv_idScomRegData.end() != chip_itr) |
| 104 | { |
| 105 | auto addr_itr = chip_itr->second.find(i_address); |
| 106 | ASSERT_EQ(chip_itr->second.end(), addr_itr); |
| 107 | } |
| 108 | |
| 109 | // Add the new entry. |
| 110 | iv_idScomRegData[i_chip][i_address] = i_value; |
| 111 | } |
| 112 | |
| 113 | /** @brief Adds a Signature to iv_expSigList. */ |
| 114 | void addSignature(const Signature& i_signature) |
| 115 | { |
| 116 | // First check if this entry already exists. |
| 117 | auto itr = |
| 118 | std::find(iv_expSigList.begin(), iv_expSigList.end(), i_signature); |
| 119 | ASSERT_EQ(iv_expSigList.end(), itr); |
| 120 | |
| 121 | // Add the new entry. |
| 122 | iv_expSigList.push_back(i_signature); |
| 123 | } |
| 124 | |
| 125 | /** |
| 126 | * @brief Flushes register and expected signature lists used for a single |
| 127 | * isolation. |
| 128 | */ |
| 129 | void flushIterationData() |
| 130 | { |
| 131 | iv_scomRegData.clear(); |
| 132 | iv_idScomRegData.clear(); |
| 133 | iv_expSigList.clear(); |
| 134 | } |
| 135 | |
| 136 | /** @brief Flushes all simulation data. */ |
| 137 | void flushAll() |
| 138 | { |
| 139 | flushIterationData(); |
| 140 | iv_chipList.clear(); |
| 141 | } |
| 142 | |
| 143 | /** |
| 144 | * @brief After an iteration is set up with registers and expected |
| 145 | * signatures, this is called to run the simulation and verify the |
| 146 | * expected signatures. |
| 147 | */ |
| 148 | void endIteration(); |
| 149 | }; |
| 150 | |
| 151 | } // end namespace libhei |
| 152 | |
| 153 | //------------------------------------------------------------------------------ |
| 154 | |
| 155 | // clang-format off |
| 156 | |
| 157 | // The following macros can be used to simplify commonly used function for |
| 158 | // simulation test cases. At the core of each test case is a Google Test (i.e. |
| 159 | // gtest), which will do most of the error checking. Just like in gtest, a test |
| 160 | // case file can contain more than one test. Also, remember that this is all C++ |
| 161 | // code. While it not likely to be used much, you can combine these macros with |
| 162 | // C++ code to do more advanced test cases. For example, you can put the |
| 163 | // iteration macros in a loop to walk through each bit of a register. |
| 164 | |
| 165 | /** |
| 166 | * This is the beginning of a test case. The NAME parameter must be valid C++ |
| 167 | * identifier and must not contain any underscores (per gtest requirement). To |
| 168 | * end the test case use END_TEST_CASE. All contents of the test case must be |
| 169 | * contain in between these two macros. |
| 170 | */ |
| 171 | #define START_TEST_CASE(NAME) \ |
| 172 | TEST(Simulator, NAME) \ |
| 173 | { \ |
| 174 | libhei::SimulatorData& simData = \ |
| 175 | libhei::SimulatorData::getSingleton(); \ |
Zane Shelley | 1be4c3c | 2020-04-17 15:55:07 -0500 | [diff] [blame^] | 176 | simData.flushAll(); \ |
| 177 | libhei::ChipType_t chipType; |
Zane Shelley | 11b8994 | 2019-11-07 11:07:28 -0600 | [diff] [blame] | 178 | |
| 179 | /** |
| 180 | * Use this to configure a chip object for the test case. There should be an |
| 181 | * instance of this macro for each chip required for the test case. Note that |
| 182 | * this will also call libhei::initialize() for each new chip type. The CHIP |
| 183 | * parameter must be valid C++ identifier because it will be used as the name of |
| 184 | * the chip variable. This same identifier will be re-used in several other |
| 185 | * macros. |
| 186 | */ |
| 187 | #define CHIP(CHIP, TYPE) \ |
Zane Shelley | 1be4c3c | 2020-04-17 15:55:07 -0500 | [diff] [blame^] | 188 | chipType = static_cast<libhei::ChipType_t>(libhei::SimulatorData::TYPE); \ |
| 189 | libhei::Chip CHIP{#CHIP, chipType}; \ |
Zane Shelley | 11b8994 | 2019-11-07 11:07:28 -0600 | [diff] [blame] | 190 | simData.addChip(CHIP); |
| 191 | |
| 192 | /** |
| 193 | * Once all of the chips have been configured, there can be one or more |
| 194 | * iterations defined in the test case. Use END_ITERATION to end the iteration. |
| 195 | * Note that register and signature information will be reset for each |
| 196 | * iteration, however, the same set of configure chips will be used for all |
| 197 | * iterations within the test case. |
| 198 | */ |
| 199 | #define START_ITERATION \ |
| 200 | { \ |
| 201 | simData.flushIterationData(); |
| 202 | |
| 203 | /** This will add a SCOM register to the current iteration. */ |
| 204 | #define REG_SCOM(CHIP, ADDR, VAL) \ |
| 205 | simData.addScomReg(CHIP, static_cast<uint32_t>(ADDR), \ |
| 206 | static_cast<uint64_t>(VAL)); |
| 207 | |
| 208 | /** This will add an Indirect SCOM register to the current iteration. */ |
| 209 | #define REG_IDSCOM(CHIP, ADDR, VAL) \ |
| 210 | simData.addIdScomReg(CHIP, static_cast<uint64_t>(ADDR), \ |
| 211 | static_cast<uint64_t>(VAL)); |
| 212 | |
| 213 | /** This will add an expected signature to the current iteration. */ |
| 214 | #define EXP_SIG(CHIP, ID, INST, BIT, TYPE) \ |
| 215 | simData.addSignature(libhei::Signature{ \ |
| 216 | CHIP, static_cast<libhei::RegisterId_t>(ID), \ |
| 217 | static_cast<libhei::RegisterInstance_t>(INST), \ |
| 218 | static_cast<libhei::RegisterBit_t>(BIT), libhei::ATTN_TYPE_##TYPE}); |
| 219 | |
| 220 | /** |
| 221 | * This is the end of an iteration that began with START_ITERATION. All of the |
| 222 | * register contents and expected signatures will have been stored in the |
| 223 | * simulation data. So, this will call libhei::isolate() with the list of |
| 224 | * configured chips. Using the register contents in the simulation data, |
| 225 | * libhei::isolate() will return a list of signatures (active attentions). That |
| 226 | * list will be compared against the expected list of signatures stored in the |
| 227 | * simulation data for test case verification. |
| 228 | * |
| 229 | * You will see that there are two gtest checks for failures: |
| 230 | * - The first check will look to see if any of the previous functions to add |
| 231 | * chips, registers, or signatures to the simulation data failed. |
| 232 | * - The second check will determine if isolation completed successfully and if |
| 233 | * all expected signatures have been verified. |
| 234 | * If either check fails, the test case will be aborted regardless if there are |
| 235 | * additional iterations in that test case. Note that failure in a test case |
| 236 | * will not have any impact on subsequent test cases. Therefore, all test cases |
| 237 | * in a file will at least be attempted even if there is a failure. |
| 238 | */ |
| 239 | #define END_ITERATION \ |
| 240 | if (HasFailure()) { simData.flushAll(); return; } \ |
| 241 | simData.endIteration(); \ |
| 242 | if (HasFailure()) { simData.flushAll(); return; } \ |
| 243 | } |
| 244 | |
| 245 | /** |
| 246 | * This is the end of the test case that started with START_TEST_CASE. It will |
| 247 | * call libhei::uninitialize() and clean up the simulation data. |
| 248 | */ |
| 249 | #define END_TEST_CASE \ |
| 250 | libhei::uninitialize(); \ |
| 251 | simData.flushAll(); \ |
| 252 | } |
| 253 | |
| 254 | // clang-format on |