Merge pull request #1321 from lnis-uofu/xt_fabric_key_assistant

Add a utility tool: fabric key assistant
This commit is contained in:
tangxifan 2023-08-26 21:32:55 -07:00 committed by GitHub
commit aef4e76afe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 479 additions and 0 deletions

View File

@ -18,3 +18,5 @@
file_formats/index
utilities/index

View File

@ -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 <string>
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 <string>
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 <string>
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
<key id="1" name="tile_0__0_" value="5" alias="tile_4__2_"/>
while the input fabric key includes a key:
.. code-block:: xml
<key id="23" alias="tile_4__2_"/>
the resulting output fabric key file includes a key:
.. code-block:: xml
<key id="23" name="tile_0__0_" value="5" alias="tile_4__2_"/>
.. option:: --verbose
To enable verbose output
.. option:: --help
Show help desk

View File

@ -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

View File

@ -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)

View File

@ -55,6 +55,35 @@ std::vector<FabricSubKeyId> 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<FabricKeyId> 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<FabricKeyId> 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<FabricKeyId> FabricKey::region_keys(
const FabricRegionId& region_id) const {
/* validate the region_id */

View File

@ -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<FabricKeyId> 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<int> 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<FabricKeyId> find_key_by_alias(const std::string& alias) const;
/* Check if there are any keys */
bool empty() const;

View File

@ -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<std::string, size_t> 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<float>(num_keys_checked) /
static_cast<float>(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<FabricKeyId> 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<std::string, std::map<size_t, size_t>> 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<float>(num_keys_checked) /
static_cast<float>(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 */

View File

@ -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

View File

@ -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<std::string> format_argv(const std::string& cmd_name,
int argc, const char** argv) {
std::vector<std::string> 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<openfpga::FabricKeyId> input_found_keys =
input_key.find_key_by_alias(curr_alias);
progress = static_cast<float>(num_keys_checked) /
static_cast<float>(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<openfpga::FabricKeyId> ref_found_keys =
ref_key.find_key_by_alias(curr_alias);
progress = static_cast<float>(num_keys_checked) /
static_cast<float>(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<std::string> 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);
}