blob: e5526d8f25d062b557919b51ce05c8f76f35d017 [file] [log] [blame]
#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;
}
public:
/** The list of supported chip types for the simulator. */
enum ChipType
{
SAMPLE = 0xdeadbeef,
};
private:
/** The Chip Data file paths for each support chip type. */
static const std::map<ChipType, const char*> cv_chipPath;
/** 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 Retrieve ScomReg from map and return its value */
uint64_t getScomReg(const Chip& i_chip, uint32_t i_address)
{
return iv_scomRegData[i_chip][i_address];
}
/** @breif Retrieve idScomReg from map and return its value */
uint64_t getIdScomReg(const Chip& i_chip, uint64_t i_address)
{
return iv_idScomRegData[i_chip][i_address];
}
/** @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(); \
libhei::ChipType_t chipType;
/**
* 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) \
chipType = static_cast<libhei::ChipType_t>(libhei::SimulatorData::TYPE); \
libhei::Chip CHIP{#CHIP, chipType}; \
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