diff --git a/docs/source/manual/index.rst b/docs/source/manual/index.rst index a00dce30a..fd4f4df56 100644 --- a/docs/source/manual/index.rst +++ b/docs/source/manual/index.rst @@ -18,3 +18,5 @@ file_formats/index + utilities/index + diff --git a/docs/source/manual/utilities/fabric_key_assistant.rst b/docs/source/manual/utilities/fabric_key_assistant.rst new file mode 100644 index 000000000..4625366d2 --- /dev/null +++ b/docs/source/manual/utilities/fabric_key_assistant.rst @@ -0,0 +1,53 @@ +.. _utility_fabric_key_assistant: + +Fabric Key Assistant +-------------------- + +Fabric Key Assistant is a tool to help users to craft fabric key files (see details in :ref:`file_formats_fabric_key`). +Note that crafting a fabric key is not an easy task for engineers, as its complexity grows exponentially with FPGA sizes. +This tool is developed to assist engineers when finalizing fabric key files. +It can apply sanity checks on hand-crafted fabric key files, helping engineers to correct and debug. + +The tool can be found at ``/build/libs/libfabrickey/test/fabric_key_assistant`` + +The tool includes the following options: + +.. option:: --reference + + Specifiy a reference fabric key file, which has been already validated by OpenFPGA. For example, the reference fabric key can be a file which is written by OpenFPGA as a default key. The reference fabric key file is treated as the baseline, on which the input fabric key file will be compared to. + + .. note:: The reference fabric key should contain all the syntax, e.g., ``name``, ``value`` and ``alias``. + +.. option:: --input + + Specify the input fabric key file, which is typically hand-crafted by users. Sanity checks will be applied to the input fabric key file by comparing the reference. + + .. note:: The input fabric key should contain only the syntax ``alias``. + +.. option:: --output + + Specify the output fabric key file, which is an updated version of the input fabric key file. Difference from the input file, the output file contains ``name`` and ``value``, which is added by linking the ``alias`` from input file to reference file. For example, the reference fabric key includes a key: + +.. code-block:: xml + + + +while the input fabric key includes a key: + +.. code-block:: xml + + + +the resulting output fabric key file includes a key: + +.. code-block:: xml + + + +.. option:: --verbose + + To enable verbose output + +.. option:: --help + + Show help desk diff --git a/docs/source/manual/utilities/index.rst b/docs/source/manual/utilities/index.rst new file mode 100644 index 000000000..a51774314 --- /dev/null +++ b/docs/source/manual/utilities/index.rst @@ -0,0 +1,13 @@ +Utilities +--------- + +OpenFPGA contains a number of utility tools to help users to craft files. + + +.. _utilities: + Utility Tools + +.. toctree:: + :maxdepth: 2 + + fabric_key_assistant diff --git a/libs/libfabrickey/CMakeLists.txt b/libs/libfabrickey/CMakeLists.txt index ac43927ac..8c01cc472 100644 --- a/libs/libfabrickey/CMakeLists.txt +++ b/libs/libfabrickey/CMakeLists.txt @@ -20,6 +20,7 @@ set_target_properties(libfabrickey PROPERTIES PREFIX "") #Avoid extra 'lib' pref #Specify link-time dependancies target_link_libraries(libfabrickey libopenfpgautil + libopenfpgashell libarchopenfpga libvtrutil libpugiutil) diff --git a/libs/libfabrickey/src/base/fabric_key.cpp b/libs/libfabrickey/src/base/fabric_key.cpp index b80d87204..a6558a258 100644 --- a/libs/libfabrickey/src/base/fabric_key.cpp +++ b/libs/libfabrickey/src/base/fabric_key.cpp @@ -55,6 +55,35 @@ std::vector FabricKey::sub_keys( /************************************************************************ * Public Accessors : Basic data query ***********************************************************************/ +size_t FabricKey::num_regions() const { return region_ids_.size(); } + +size_t FabricKey::num_keys() const { return key_ids_.size(); } + +std::vector FabricKey::find_key_by_alias( + const std::string& alias) const { + /* Throw warning on empty alias which may cause unexpected results: whole key + * is dumped! */ + if (alias.empty()) { + VTR_LOG_WARN( + "Empty alias is given! This may cause unexpected results, i.e., a whole " + "data base is dumped!\n"); + } + size_t num_found_keys = 0; + for (FabricKeyId key_id : key_ids_) { + if (key_alias(key_id) == alias) { + num_found_keys++; + } + } + std::vector found_keys; + found_keys.reserve(num_found_keys); + for (FabricKeyId key_id : key_ids_) { + if (key_alias(key_id) == alias) { + found_keys.push_back(key_id); + } + } + return found_keys; +} + std::vector FabricKey::region_keys( const FabricRegionId& region_id) const { /* validate the region_id */ diff --git a/libs/libfabrickey/src/base/fabric_key.h b/libs/libfabrickey/src/base/fabric_key.h index 7afd43af7..3982cb8b8 100644 --- a/libs/libfabrickey/src/base/fabric_key.h +++ b/libs/libfabrickey/src/base/fabric_key.h @@ -75,6 +75,8 @@ class FabricKey { const FabricKeyModuleId& module_id) const; public: /* Public Accessors: Basic data query */ + size_t num_regions() const; + size_t num_keys() const; /* Access all the keys of a region */ std::vector region_keys(const FabricRegionId& region_id) const; /* Access the name of a key */ @@ -86,6 +88,12 @@ class FabricKey { /* Access the coordinate of a key */ vtr::Point key_coordinate(const FabricKeyId& key_id) const; + /** @brief Find valid key ids for a given alias. Note that you should NOT send + * an empty alias which may cause a complete list of key ids to be returned + * (extremely inefficent and NOT useful). Suggest to check if the existing + * fabric key contains valid alias for each key before calling this API!!! */ + std::vector find_key_by_alias(const std::string& alias) const; + /* Check if there are any keys */ bool empty() const; diff --git a/libs/libfabrickey/src/utils/check_fabric_key.cpp b/libs/libfabrickey/src/utils/check_fabric_key.cpp new file mode 100644 index 000000000..1e8964a33 --- /dev/null +++ b/libs/libfabrickey/src/utils/check_fabric_key.cpp @@ -0,0 +1,117 @@ +/************************************************************************ + * Check functions for the content of fabric key to avoid conflicts with + * other data structures + * These functions are not universal methods for the FabricKey class + * They are made to ease the development in some specific purposes + * Please classify such functions in this file + ***********************************************************************/ +#include "check_fabric_key.h" + +#include "vtr_assert.h" +#include "vtr_log.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/** @brief Sanity checks for fabric key alias attribute: + * - Each alias should NOT be empty + * - Each alias should be defined only once! + */ +int check_fabric_key_alias(const FabricKey& input_key, const bool& verbose) { + /* Check each key now */ + size_t num_errors = 0; + float progress = 0.; + size_t num_keys_checked = 0; + + std::map alias_count; + for (FabricKeyId key_id : input_key.keys()) { + /* Note that this is slow. May consider to build a map first */ + std::string curr_alias = input_key.key_alias(key_id); + progress = static_cast(num_keys_checked) / + static_cast(input_key.num_keys()) * 100.0; + VTR_LOGV(verbose, "[%lu%] Checking key alias '%s'\r", size_t(progress), + curr_alias.c_str()); + if (curr_alias.empty()) { + VTR_LOG_ERROR( + "Empty key alias (id='%lu') found in keys which is invalid!\n", + size_t(key_id)); + num_errors++; + } + auto result = alias_count.find(curr_alias); + if (result == alias_count.end()) { + alias_count[curr_alias] = 0; + } else { + alias_count[curr_alias] += 1; + } + num_keys_checked++; + } + for (const auto& kv : alias_count) { + if (kv.second > 1) { + std::string key_id_str; + std::vector found_keys = + input_key.find_key_by_alias(kv.first); + for (FabricKeyId found_key_id : found_keys) { + key_id_str += std::to_string(size_t(found_key_id)) + ","; + } + key_id_str.pop_back(); /* Remove last comma */ + VTR_LOG_ERROR( + "Duplicated key alias '%s' found %lu times in keys (ids: %s), which is " + "invalid!\n", + kv.first.c_str(), kv.second, key_id_str.c_str()); + num_errors++; + } + } + + return num_errors; +} + +/** @brief Sanity checks for fabric key name and value attribute: + * - Each name should not be empty + * - Each value should be larger than zero ! + */ +int check_fabric_key_names_and_values(const FabricKey& input_key, + const bool& verbose) { + /* Check each key now */ + size_t num_errors = 0; + float progress = 0.; + size_t num_keys_checked = 0; + + std::map> key_value_count; + for (FabricKeyId key_id : input_key.keys()) { + /* Note that this is slow. May consider to build a map first */ + std::string curr_name = input_key.key_name(key_id); + size_t curr_value = input_key.key_value(key_id); + progress = static_cast(num_keys_checked) / + static_cast(input_key.num_keys()) * 100.0; + VTR_LOGV(verbose, "[%lu%] Checking key names and values '(%s, %lu)'\r", + size_t(progress), curr_name.c_str(), curr_value); + if (curr_name.empty()) { + VTR_LOG_ERROR( + "Empty key name (id='%lu') found in keys which is invalid!\n", + size_t(key_id)); + num_errors++; + } + auto result = key_value_count[curr_name].find(curr_value); + if (result == key_value_count[curr_name].end()) { + key_value_count[curr_name][curr_value] = 0; + } else { + key_value_count[curr_name][curr_value] += 1; + } + num_keys_checked++; + } + for (const auto& key_name_kv : key_value_count) { + for (const auto& key_value_kv : key_name_kv.second) { + if (key_value_kv.second > 1) { + VTR_LOG_ERROR( + "Duplicated key name and value pair (%s, %lu) found %lu times in " + "keys, which is invalid!\n", + key_name_kv.first.c_str(), key_value_kv.first, key_value_kv.second); + num_errors++; + } + } + } + + return num_errors; +} + +} /* end namespace openfpga */ diff --git a/libs/libfabrickey/src/utils/check_fabric_key.h b/libs/libfabrickey/src/utils/check_fabric_key.h new file mode 100644 index 000000000..63f3f31db --- /dev/null +++ b/libs/libfabrickey/src/utils/check_fabric_key.h @@ -0,0 +1,23 @@ +#ifndef CHECK_FABRIC_KEY_H +#define CHECK_FABRIC_KEY_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include "fabric_key.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +int check_fabric_key_alias(const FabricKey& input_key, const bool& verbose); + +int check_fabric_key_names_and_values(const FabricKey& input_key, + const bool& verbose); + +} /* end namespace openfpga */ + +#endif diff --git a/libs/libfabrickey/test/fabric_key_assistant.cpp b/libs/libfabrickey/test/fabric_key_assistant.cpp new file mode 100644 index 000000000..c0befce57 --- /dev/null +++ b/libs/libfabrickey/test/fabric_key_assistant.cpp @@ -0,0 +1,233 @@ +/******************************************************************** + * Unit test functions to validate the correctness of + * 1. parser of data structures + * 2. writer of data structures + *******************************************************************/ +/* Headers from vtrutils */ +#include "vtr_assert.h" +#include "vtr_log.h" + +/* Headers from fabric key */ +#include "check_fabric_key.h" +#include "command_echo.h" +#include "command_exit_codes.h" +#include "command_parser.h" +#include "read_xml_fabric_key.h" +#include "write_xml_fabric_key.h" + +/** @brief Initialize the options from command-line inputs and organize in the + * format that is ready for parsing */ +static std::vector format_argv(const std::string& cmd_name, + int argc, const char** argv) { + std::vector cmd_opts; + cmd_opts.push_back(cmd_name); + for (int iarg = 1; iarg < argc; ++iarg) { + cmd_opts.push_back(std::string(argv[iarg])); + } + return cmd_opts; +} + +/** @brief Checks to be done: + * - Each alias of reference key can be found in the input key + */ +static int check_input_and_ref_key_alias_match( + const openfpga::FabricKey& input_key, const openfpga::FabricKey& ref_key, + const bool& verbose) { + size_t num_errors = 0; + size_t num_keys_checked = 0; + float progress = 0.; + VTR_LOG( + "Checking key alias matching between reference key and input keys...\n"); + for (openfpga::FabricKeyId key_id : ref_key.keys()) { + /* Note that this is slow. May consider to build a map first */ + std::string curr_alias = ref_key.key_alias(key_id); + std::vector input_found_keys = + input_key.find_key_by_alias(curr_alias); + progress = static_cast(num_keys_checked) / + static_cast(ref_key.num_keys()) * 100.0; + VTR_LOGV(verbose, "[%lu%] Checking key alias '%s'\r", size_t(progress), + curr_alias.c_str()); + if (input_found_keys.empty()) { + VTR_LOG_ERROR( + "Invalid alias '%s' in the reference key (id='%lu'), which does not " + "exist in the input key!\n", + curr_alias.c_str(), size_t(key_id)); + num_errors++; + } + if (input_found_keys.size() > 1) { + VTR_LOG_ERROR( + "Invalid alias '%s' in the input key (id='%lu'), which have been " + "found %lu times!\n", + curr_alias.c_str(), size_t(key_id), input_found_keys.size()); + num_errors++; + } + num_keys_checked++; + } + VTR_LOG( + "Checking key alias matching between reference key and input keys... %s\n", + num_errors ? "[Fail]" : "[Pass]"); + return num_errors ? openfpga::CMD_EXEC_FATAL_ERROR + : openfpga::CMD_EXEC_SUCCESS; +} + +/** @brief Checks to be done: + * - Number of configuration regions match + * - Number of keys match + */ +static int check_input_key(const openfpga::FabricKey& input_key, + const openfpga::FabricKey& ref_key, + const bool& verbose) { + if (ref_key.num_regions() != input_key.num_regions()) { + VTR_LOG_ERROR( + "Different number of configuration regions between reference key " + "(='%lu') and input key ('=%lu')!\n", + ref_key.num_regions(), input_key.num_regions()); + return openfpga::CMD_EXEC_FATAL_ERROR; + } + if (ref_key.num_keys() != input_key.num_keys()) { + VTR_LOG_ERROR( + "Different number of keys between reference key (='%lu') and input key " + "('=%lu')!\n", + ref_key.num_keys(), input_key.num_keys()); + return openfpga::CMD_EXEC_FATAL_ERROR; + } + size_t num_errors = 0; + size_t curr_num_err = 0; + VTR_LOG("Checking key alias in reference key...\n"); + curr_num_err = openfpga::check_fabric_key_alias(ref_key, verbose); + VTR_LOG("Checking key alias in reference key... %s\n", + curr_num_err ? "[Fail]" : "[Pass]"); + VTR_LOG("Checking key names and values in reference key...\n"); + curr_num_err = openfpga::check_fabric_key_names_and_values(ref_key, verbose); + num_errors += curr_num_err; + VTR_LOG("Checking key names and valus in reference key... %s\n", + curr_num_err ? "[Fail]" : "[Pass]"); + VTR_LOG("Checking key alias in input key...\n"); + curr_num_err = openfpga::check_fabric_key_alias(input_key, verbose); + num_errors += curr_num_err; + VTR_LOG("Checking key alias in input key... %s\n", + curr_num_err ? "[Fail]" : "[Pass]"); + num_errors += + check_input_and_ref_key_alias_match(input_key, ref_key, verbose); + return num_errors ? openfpga::CMD_EXEC_FATAL_ERROR + : openfpga::CMD_EXEC_SUCCESS; +} + +/** @brief Checks to be done: + * - Each alias of input key can be found in the reference key + * - Update input key with pair of name and value which matches the alias from + * the reference key + */ +static int update_input_key(openfpga::FabricKey& input_key, + const openfpga::FabricKey& ref_key, + const bool& verbose) { + size_t num_errors = 0; + size_t num_keys_checked = 0; + float progress = 0.; + VTR_LOG("Pairing key alias between reference key and input keys...\n"); + for (openfpga::FabricKeyId key_id : input_key.keys()) { + /* Note that this is slow. May consider to build a map first */ + std::string curr_alias = input_key.key_alias(key_id); + std::vector ref_found_keys = + ref_key.find_key_by_alias(curr_alias); + progress = static_cast(num_keys_checked) / + static_cast(input_key.num_keys()) * 100.0; + VTR_LOGV(verbose, "[%lu%] Pairing key alias '%s'\r", size_t(progress), + curr_alias.c_str()); + if (ref_found_keys.empty()) { + VTR_LOG_ERROR( + "Invalid alias '%s' in the input key (id='%lu'), which does not " + "exist in the reference key!\n", + curr_alias.c_str(), size_t(key_id)); + num_errors++; + } + if (ref_found_keys.size() > 1) { + VTR_LOG_ERROR( + "Invalid alias '%s' in the reference key (id='%lu'), which have been " + "found %lu times!\n", + curr_alias.c_str(), size_t(key_id)); + num_errors++; + } + /* Now we have a key, get the name and value, and update input key */ + input_key.set_key_name(key_id, ref_key.key_name(ref_found_keys[0])); + input_key.set_key_value(key_id, ref_key.key_value(ref_found_keys[0])); + VTR_LOGV(verbose, "[%lu%] Pairing key alias '%s' -> ('%s', %lu)\r", + size_t(progress), curr_alias.c_str(), + input_key.key_name(key_id).c_str(), input_key.key_value(key_id)); + num_keys_checked++; + } + return num_errors ? openfpga::CMD_EXEC_FATAL_ERROR + : openfpga::CMD_EXEC_SUCCESS; +} + +/** @brief Checks to be done: + * - Number of configuration regions match + * - Number of keys match + * - Each alias can be found in the reference key + */ +static int check_and_update_input_key(openfpga::FabricKey& input_key, + const openfpga::FabricKey& ref_key, + const bool& verbose) { + int status = openfpga::CMD_EXEC_SUCCESS; + status = check_input_key(input_key, ref_key, verbose); + if (status != openfpga::CMD_EXEC_SUCCESS) { + return openfpga::CMD_EXEC_FATAL_ERROR; + } + return update_input_key(input_key, ref_key, verbose); +} + +int main(int argc, const char** argv) { + /* Create a new command and Initialize the options available in the user + * interface */ + openfpga::Command cmd("fabric_key_assistant"); + openfpga::CommandOptionId opt_ref = + cmd.add_option("reference", true, "Specify the reference fabric key file"); + cmd.set_option_require_value(opt_ref, openfpga::OPT_STRING); + openfpga::CommandOptionId opt_input = + cmd.add_option("input", true, "Specify the hand-crafted fabric key file"); + cmd.set_option_require_value(opt_input, openfpga::OPT_STRING); + openfpga::CommandOptionId opt_output = cmd.add_option( + "output", true, "Specify the final fabric key file to be outputted"); + cmd.set_option_require_value(opt_output, openfpga::OPT_STRING); + openfpga::CommandOptionId opt_verbose = + cmd.add_option("verbose", false, "Show verbose outputs"); + openfpga::CommandOptionId opt_help = + cmd.add_option("help", false, "Show help desk"); + + /* Parse the option, to avoid issues, we use the command name to replace the + * argv[0] */ + std::vector cmd_opts = format_argv(cmd.name(), argc, argv); + + openfpga::CommandContext cmd_ctx(cmd); + if (false == parse_command(cmd_opts, cmd, cmd_ctx) || + cmd_ctx.option_enable(cmd, opt_help)) { + /* Echo the command */ + print_command_options(cmd); + return openfpga::CMD_EXEC_FATAL_ERROR; + } else { + /* Let user to confirm selected options */ + print_command_context(cmd, cmd_ctx); + } + + /* Parse the fabric key from an XML file */ + VTR_LOG("Read the reference fabric key from an XML file: %s.\n", + cmd_ctx.option_value(cmd, opt_ref).c_str()); + openfpga::FabricKey ref_key = + openfpga::read_xml_fabric_key(cmd_ctx.option_value(cmd, opt_ref).c_str()); + + VTR_LOG("Read the hand-crafted fabric key from an XML file: %s.\n", + cmd_ctx.option_value(cmd, opt_input).c_str()); + openfpga::FabricKey input_key = + openfpga::read_xml_fabric_key(cmd_ctx.option_value(cmd, opt_input).c_str()); + + /* Check the input key */ + if (check_and_update_input_key(input_key, ref_key, + cmd_ctx.option_enable(cmd, opt_verbose))) { + return openfpga::CMD_EXEC_FATAL_ERROR; + } + + VTR_LOG("Write the final fabric key to an XML file: %s.\n", + cmd_ctx.option_value(cmd, opt_output).c_str()); + return openfpga::write_xml_fabric_key( + cmd_ctx.option_value(cmd, opt_output).c_str(), input_key); +}